refactor(agent): consolidate agent system into unified AgentManager with provider-based architecture

Replace separate ACP and agent-types modules with unified AgentManager using provider pattern. Migrate Claude and Codex implementations to provider-specific clients. Streamline session-agent integration, remove old ACP notification types, and introduce agent timeline events. Delete obsolete documentation and test files. Add AgentRegistry for persistence and agent-stream-based communication pattern.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-11-15 02:06:09 +01:00
co-authored by Claude
parent 90b0094aae
commit 8cf808b14d
55 changed files with 2770 additions and 7042 deletions
@@ -1,4 +1,5 @@
import { useState, useRef, useEffect, useMemo, useCallback } from "react";
import type { ReactElement } from "react";
import {
View,
Text,
@@ -28,30 +29,24 @@ import { useSession } from "@/contexts/session-context";
import { useRouter } from "expo-router";
import { generateMessageId } from "@/types/stream";
import {
listAgentTypeDefinitions,
type AgentType,
type AgentTypeDefinition,
type AgentModeDefinition,
} from "@server/server/acp/agent-types";
AGENT_PROVIDER_DEFINITIONS,
type AgentProviderDefinition,
} from "@server/server/agent/provider-manifest";
import type { AgentProvider, AgentMode, AgentSessionConfig } from "@server/server/agent/agent-sdk-types";
interface CreateAgentModalProps {
isVisible: boolean;
onClose: () => void;
}
const agentTypeDefinitions = listAgentTypeDefinitions();
const providerDefinitions = AGENT_PROVIDER_DEFINITIONS;
const providerDefinitionMap = new Map<AgentProvider, AgentProviderDefinition>(
providerDefinitions.map((definition) => [definition.id, definition])
);
const agentTypeDefinitionMap: Record<AgentType, AgentTypeDefinition> =
{} as Record<AgentType, AgentTypeDefinition>;
for (const definition of agentTypeDefinitions) {
agentTypeDefinitionMap[definition.id] = definition;
}
const fallbackDefinition = agentTypeDefinitionMap.claude ?? agentTypeDefinitions[0];
const DEFAULT_AGENT_TYPE: AgentType = fallbackDefinition
? fallbackDefinition.id
: "claude";
const DEFAULT_MODE_FOR_DEFAULT_AGENT = fallbackDefinition?.defaultModeId ?? "";
const fallbackDefinition = providerDefinitions[0];
const DEFAULT_PROVIDER: AgentProvider = fallbackDefinition?.id ?? "claude";
const DEFAULT_MODE_FOR_DEFAULT_PROVIDER = fallbackDefinition?.defaultModeId ?? "";
const BACKDROP_OPACITY = 0.55;
export function CreateAgentModal({
@@ -71,10 +66,10 @@ export function CreateAgentModal({
const [isMounted, setIsMounted] = useState(isVisible);
const [workingDir, setWorkingDir] = useState("");
const [selectedAgentType, setSelectedAgentType] = useState<AgentType>(
DEFAULT_AGENT_TYPE
const [selectedProvider, setSelectedProvider] = useState<AgentProvider>(
DEFAULT_PROVIDER
);
const [selectedMode, setSelectedMode] = useState(DEFAULT_MODE_FOR_DEFAULT_AGENT);
const [selectedMode, setSelectedMode] = useState(DEFAULT_MODE_FOR_DEFAULT_PROVIDER);
const [worktreeName, setWorktreeName] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const [isLoading, setIsLoading] = useState(false);
@@ -82,8 +77,8 @@ export function CreateAgentModal({
const pendingNavigationAgentIdRef = useRef<string | null>(null);
const agentDefinition = agentTypeDefinitionMap[selectedAgentType];
const modeOptions = agentDefinition?.availableModes ?? [];
const agentDefinition = providerDefinitionMap.get(selectedProvider);
const modeOptions = agentDefinition?.modes ?? [];
useEffect(() => {
if (!agentDefinition) {
@@ -109,8 +104,8 @@ export function CreateAgentModal({
const resetFormState = useCallback(() => {
setWorkingDir("");
setWorktreeName("");
setSelectedAgentType(DEFAULT_AGENT_TYPE);
setSelectedMode(DEFAULT_MODE_FOR_DEFAULT_AGENT);
setSelectedProvider(DEFAULT_PROVIDER);
setSelectedMode(DEFAULT_MODE_FOR_DEFAULT_PROVIDER);
setErrorMessage("");
setIsLoading(false);
setPendingRequestId(null);
@@ -293,14 +288,17 @@ export function CreateAgentModal({
setPendingRequestId(requestId);
setErrorMessage("");
const modeId =
modeOptions.length > 0 && selectedMode !== "" ? selectedMode : undefined;
const modeId = modeOptions.length > 0 && selectedMode !== "" ? selectedMode : undefined;
const config: AgentSessionConfig = {
provider: selectedProvider,
cwd: trimmedPath,
...(modeId ? { modeId } : {}),
};
try {
createAgent({
cwd: trimmedPath,
agentType: selectedAgentType,
initialMode: modeId,
config,
worktreeName: worktree || undefined,
requestId,
});
@@ -314,8 +312,8 @@ export function CreateAgentModal({
workingDir,
worktreeName,
selectedMode,
selectedAgentType,
modeOptions,
selectedProvider,
isLoading,
validateWorktreeName,
addRecentPath,
@@ -327,20 +325,22 @@ export function CreateAgentModal({
return;
}
const unsubscribe = ws.on("agent_created", (message) => {
if (message.type !== "agent_created") {
const unsubscribe = ws.on("status", (message) => {
if (message.type !== "status") {
return;
}
const { agentId, requestId } = message.payload;
if (requestId === pendingRequestId) {
console.log("[CreateAgentModal] Agent created:", agentId);
setIsLoading(false);
setPendingRequestId(null);
pendingNavigationAgentIdRef.current = agentId;
handleClose();
const payload = message.payload as { status: string; agentId?: string; requestId?: string };
if (payload.status !== "agent_created") {
return;
}
if (payload.requestId !== pendingRequestId || !payload.agentId) {
return;
}
console.log("[CreateAgentModal] Agent created:", payload.agentId);
setIsLoading(false);
setPendingRequestId(null);
pendingNavigationAgentIdRef.current = payload.agentId;
handleClose();
});
return () => {
@@ -427,11 +427,11 @@ export function CreateAgentModal({
]}
>
<AssistantSelector
agentTypeDefinitions={agentTypeDefinitions}
providerDefinitions={providerDefinitions}
disabled={isLoading}
isStacked={isCompactLayout}
selectedAgentType={selectedAgentType}
onSelect={setSelectedAgentType}
selectedProvider={selectedProvider}
onSelect={setSelectedProvider}
/>
<ModeSelector
disabled={isLoading}
@@ -496,12 +496,12 @@ interface ModalHeaderProps {
onClose: () => void;
}
function ModalHeader({ paddingTop, paddingLeft, paddingRight, onClose }: ModalHeaderProps): JSX.Element {
function ModalHeader({ paddingTop, paddingLeft, paddingRight, onClose }: ModalHeaderProps): ReactElement {
return (
<View style={[styles.header, { paddingTop, paddingLeft, paddingRight }]}>
<Text style={styles.headerTitle}>Create New Agent</Text>
<Pressable onPress={onClose} style={styles.closeButton} hitSlop={8}>
<X size={20} style={styles.closeIcon} />
<X size={20} color={defaultTheme.colors.mutedForeground} />
</Pressable>
</View>
);
@@ -521,7 +521,7 @@ function WorkingDirectorySection({
errorMessage,
recentPaths,
onChangeWorkingDir,
}: WorkingDirectorySectionProps): JSX.Element {
}: WorkingDirectorySectionProps): ReactElement {
return (
<View style={styles.formSection}>
<Text style={styles.label}>Working Directory</Text>
@@ -557,26 +557,26 @@ function WorkingDirectorySection({
}
interface AssistantSelectorProps {
agentTypeDefinitions: AgentTypeDefinition[];
selectedAgentType: AgentType;
providerDefinitions: AgentProviderDefinition[];
selectedProvider: AgentProvider;
disabled: boolean;
isStacked: boolean;
onSelect: (agentType: AgentType) => void;
onSelect: (provider: AgentProvider) => void;
}
function AssistantSelector({
agentTypeDefinitions,
selectedAgentType,
providerDefinitions,
selectedProvider,
disabled,
isStacked,
onSelect,
}: AssistantSelectorProps): JSX.Element {
}: AssistantSelectorProps): ReactElement {
return (
<View style={[styles.selectorColumn, isStacked && styles.selectorColumnFull]}>
<Text style={styles.label}>Assistant</Text>
<View style={styles.optionGroup}>
{agentTypeDefinitions.map((definition) => {
const isSelected = selectedAgentType === definition.id;
{providerDefinitions.map((definition) => {
const isSelected = selectedProvider === definition.id;
return (
<Pressable
key={definition.id}
@@ -608,7 +608,7 @@ function AssistantSelector({
}
interface ModeSelectorProps {
modeOptions: AgentModeDefinition[];
modeOptions: AgentMode[];
selectedMode: string;
disabled: boolean;
isStacked: boolean;
@@ -621,7 +621,7 @@ function ModeSelector({
disabled,
isStacked,
onSelect,
}: ModeSelectorProps): JSX.Element {
}: ModeSelectorProps): ReactElement {
return (
<View style={[styles.selectorColumn, isStacked && styles.selectorColumnFull]}>
<Text style={styles.label}>Permissions</Text>
@@ -652,7 +652,7 @@ function ModeSelector({
{isSelected ? <View style={styles.radioInner} /> : null}
</View>
<View style={styles.modeTextContainer}>
<Text style={styles.optionLabel}>{mode.name}</Text>
<Text style={styles.optionLabel}>{mode.label}</Text>
{mode.description ? <Text style={styles.modeDescription}>{mode.description}</Text> : null}
</View>
</View>
@@ -671,7 +671,7 @@ interface WorktreeSectionProps {
onChange: (value: string) => void;
}
function WorktreeSection({ value, isLoading, onChange }: WorktreeSectionProps): JSX.Element {
function WorktreeSection({ value, isLoading, onChange }: WorktreeSectionProps): ReactElement {
return (
<View style={styles.formSection}>
<Text style={styles.label}>Worktree Name (Optional)</Text>
@@ -742,9 +742,6 @@ const styles = StyleSheet.create((theme) => ({
alignItems: "center",
justifyContent: "center",
},
closeIcon: {
color: theme.colors.mutedForeground,
},
scroll: {
flex: 1,
},