Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/__tests__/integration/group-chat-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { describe, it, expect, beforeAll } from 'vitest';
import { spawn } from 'child_process';
import { promisify } from 'util';
import { exec } from 'child_process';
import { getAgentCapabilities } from '../../main/agent-capabilities';
import { getAgentCapabilities } from '../../main/agents';

const execAsync = promisify(exec);

Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/integration/group-chat.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '../../main/group-chat/group-chat-moderator';
import { addParticipant } from '../../main/group-chat/group-chat-agent';
import { routeUserMessage } from '../../main/group-chat/group-chat-router';
import { AgentDetector } from '../../main/agent-detector';
import { AgentDetector } from '../../main/agents';
import {
selectTestAgents,
waitForAgentResponse,
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/integration/provider-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { exec } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { getAgentCapabilities } from '../../main/agent-capabilities';
import { getAgentCapabilities } from '../../main/agents';
import { buildSshCommand, buildRemoteCommand } from '../../main/utils/ssh-command-builder';
import type { SshRemoteConfig } from '../../shared/types';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
AGENT_CAPABILITIES,
getAgentCapabilities,
hasCapability,
} from '../../main/agent-capabilities';
} from '../../../main/agents';

describe('agent-capabilities', () => {
describe('AgentCapabilities interface', () => {
Expand Down
253 changes: 253 additions & 0 deletions src/__tests__/main/agents/definitions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
/**
* Tests for agent-definitions.ts
*
* Tests the agent definition data structures and helper functions.
*/

import { describe, it, expect } from 'vitest';
import {
AGENT_DEFINITIONS,
getAgentDefinition,
getAgentIds,
getVisibleAgentDefinitions,
type AgentDefinition,
type AgentConfigOption,
} from '../../../main/agents';

describe('agent-definitions', () => {
describe('AGENT_DEFINITIONS', () => {
it('should contain all expected agents', () => {
const agentIds = AGENT_DEFINITIONS.map((def) => def.id);

expect(agentIds).toContain('terminal');
expect(agentIds).toContain('claude-code');
expect(agentIds).toContain('codex');
expect(agentIds).toContain('opencode');
expect(agentIds).toContain('gemini-cli');
expect(agentIds).toContain('qwen3-coder');
expect(agentIds).toContain('aider');
});

it('should have required properties on all definitions', () => {
for (const def of AGENT_DEFINITIONS) {
expect(def.id).toBeDefined();
expect(def.name).toBeDefined();
expect(def.binaryName).toBeDefined();
expect(def.command).toBeDefined();
expect(def.args).toBeDefined();
expect(Array.isArray(def.args)).toBe(true);
}
});

it('should have terminal as a hidden agent', () => {
const terminal = AGENT_DEFINITIONS.find((def) => def.id === 'terminal');
expect(terminal?.hidden).toBe(true);
});

it('should have claude-code with correct base args', () => {
const claudeCode = AGENT_DEFINITIONS.find((def) => def.id === 'claude-code');
expect(claudeCode).toBeDefined();
expect(claudeCode?.args).toContain('--print');
expect(claudeCode?.args).toContain('--verbose');
expect(claudeCode?.args).toContain('--output-format');
expect(claudeCode?.args).toContain('stream-json');
expect(claudeCode?.args).toContain('--dangerously-skip-permissions');
});

it('should have codex with batch mode configuration', () => {
const codex = AGENT_DEFINITIONS.find((def) => def.id === 'codex');
expect(codex).toBeDefined();
expect(codex?.batchModePrefix).toEqual(['exec']);
expect(codex?.batchModeArgs).toContain('--dangerously-bypass-approvals-and-sandbox');
expect(codex?.jsonOutputArgs).toEqual(['--json']);
});

it('should have opencode with batch mode configuration', () => {
const opencode = AGENT_DEFINITIONS.find((def) => def.id === 'opencode');
expect(opencode).toBeDefined();
expect(opencode?.batchModePrefix).toEqual(['run']);
expect(opencode?.jsonOutputArgs).toEqual(['--format', 'json']);
expect(opencode?.noPromptSeparator).toBe(true);
});

it('should have opencode with default env vars for YOLO mode', () => {
const opencode = AGENT_DEFINITIONS.find((def) => def.id === 'opencode');
expect(opencode?.defaultEnvVars).toBeDefined();
expect(opencode?.defaultEnvVars?.OPENCODE_CONFIG_CONTENT).toContain('external_directory');
});
});

describe('getAgentDefinition', () => {
it('should return definition for valid agent ID', () => {
const claudeCode = getAgentDefinition('claude-code');
expect(claudeCode).toBeDefined();
expect(claudeCode?.id).toBe('claude-code');
expect(claudeCode?.name).toBe('Claude Code');
});

it('should return undefined for invalid agent ID', () => {
const invalid = getAgentDefinition('non-existent-agent');
expect(invalid).toBeUndefined();
});

it('should return definition for all known agents', () => {
const knownAgents = ['terminal', 'claude-code', 'codex', 'opencode', 'gemini-cli', 'aider'];
for (const agentId of knownAgents) {
const def = getAgentDefinition(agentId);
expect(def).toBeDefined();
expect(def?.id).toBe(agentId);
}
});
});

describe('getAgentIds', () => {
it('should return array of all agent IDs', () => {
const ids = getAgentIds();
expect(Array.isArray(ids)).toBe(true);
expect(ids.length).toBeGreaterThan(0);
expect(ids).toContain('claude-code');
expect(ids).toContain('terminal');
});

it('should match AGENT_DEFINITIONS length', () => {
const ids = getAgentIds();
expect(ids.length).toBe(AGENT_DEFINITIONS.length);
});
});

describe('getVisibleAgentDefinitions', () => {
it('should not include hidden agents', () => {
const visible = getVisibleAgentDefinitions();
const visibleIds = visible.map((def) => def.id);

// Terminal should be hidden
expect(visibleIds).not.toContain('terminal');
});

it('should include visible agents', () => {
const visible = getVisibleAgentDefinitions();
const visibleIds = visible.map((def) => def.id);

expect(visibleIds).toContain('claude-code');
expect(visibleIds).toContain('codex');
expect(visibleIds).toContain('opencode');
});

it('should return fewer items than AGENT_DEFINITIONS', () => {
const visible = getVisibleAgentDefinitions();
expect(visible.length).toBeLessThan(AGENT_DEFINITIONS.length);
});
});

describe('Agent argument builders', () => {
it('should have resumeArgs function for claude-code', () => {
const claudeCode = getAgentDefinition('claude-code');
expect(claudeCode?.resumeArgs).toBeDefined();
expect(typeof claudeCode?.resumeArgs).toBe('function');

const args = claudeCode?.resumeArgs?.('test-session-123');
expect(args).toEqual(['--resume', 'test-session-123']);
});

it('should have resumeArgs function for codex', () => {
const codex = getAgentDefinition('codex');
expect(codex?.resumeArgs).toBeDefined();

const args = codex?.resumeArgs?.('thread-456');
expect(args).toEqual(['resume', 'thread-456']);
});

it('should have resumeArgs function for opencode', () => {
const opencode = getAgentDefinition('opencode');
expect(opencode?.resumeArgs).toBeDefined();

const args = opencode?.resumeArgs?.('session-789');
expect(args).toEqual(['--session', 'session-789']);
});

it('should have modelArgs function for opencode', () => {
const opencode = getAgentDefinition('opencode');
expect(opencode?.modelArgs).toBeDefined();

const args = opencode?.modelArgs?.('ollama/qwen3:8b');
expect(args).toEqual(['--model', 'ollama/qwen3:8b']);
});

it('should have workingDirArgs function for codex', () => {
const codex = getAgentDefinition('codex');
expect(codex?.workingDirArgs).toBeDefined();

const args = codex?.workingDirArgs?.('/path/to/project');
expect(args).toEqual(['-C', '/path/to/project']);
});

it('should have imageArgs function for codex', () => {
const codex = getAgentDefinition('codex');
expect(codex?.imageArgs).toBeDefined();

const args = codex?.imageArgs?.('/path/to/image.png');
expect(args).toEqual(['-i', '/path/to/image.png']);
});

it('should have imageArgs function for opencode', () => {
const opencode = getAgentDefinition('opencode');
expect(opencode?.imageArgs).toBeDefined();

const args = opencode?.imageArgs?.('/path/to/image.png');
expect(args).toEqual(['-f', '/path/to/image.png']);
});
});

describe('Agent config options', () => {
it('should have configOptions for codex', () => {
const codex = getAgentDefinition('codex');
expect(codex?.configOptions).toBeDefined();
expect(Array.isArray(codex?.configOptions)).toBe(true);

const contextWindowOption = codex?.configOptions?.find((opt) => opt.key === 'contextWindow');
expect(contextWindowOption).toBeDefined();
expect(contextWindowOption?.type).toBe('number');
expect(contextWindowOption?.default).toBe(400000);
});

it('should have configOptions for opencode', () => {
const opencode = getAgentDefinition('opencode');
expect(opencode?.configOptions).toBeDefined();

const modelOption = opencode?.configOptions?.find((opt) => opt.key === 'model');
expect(modelOption).toBeDefined();
expect(modelOption?.type).toBe('text');
expect(modelOption?.default).toBe('');

// Test argBuilder
expect(modelOption?.argBuilder).toBeDefined();
expect(modelOption?.argBuilder?.('ollama/qwen3:8b')).toEqual(['--model', 'ollama/qwen3:8b']);
expect(modelOption?.argBuilder?.('')).toEqual([]);
expect(modelOption?.argBuilder?.(' ')).toEqual([]);
});
});

describe('Type definitions', () => {
it('should export AgentDefinition type', () => {
const def: AgentDefinition = {
id: 'test',
name: 'Test Agent',
binaryName: 'test',
command: 'test',
args: [],
};
expect(def.id).toBe('test');
});

it('should export AgentConfigOption type', () => {
const option: AgentConfigOption = {
key: 'testKey',
type: 'text',
label: 'Test Label',
description: 'Test description',
default: 'default value',
};
expect(option.key).toBe('testKey');
});
});
});
Loading