Remove the 5-model limit on multi-run groups
The per-group cap of 5 models was enforced in three client-side locations with no server-side constraint and no technical justification; the store iterates models in a plain loop and dispatches each run via Promise.allSettled. Remove the cap entirely from MultiRunLauncher, useMultiRunStore, and AgentManagerEmptyState, and drop the {max} ceiling from the launcher info copy across all locales.
Closes #2847
This commit is contained in:
@@ -32,7 +32,6 @@ import { startDesktopWindowDrag } from '@/lib/desktopNative';
|
|||||||
import { useI18n } from '@/lib/i18n';
|
import { useI18n } from '@/lib/i18n';
|
||||||
|
|
||||||
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
||||||
const MAX_MODELS_PER_GROUP = 5;
|
|
||||||
|
|
||||||
interface MultiRunAttachedFile {
|
interface MultiRunAttachedFile {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -727,7 +726,6 @@ const RunGroupCard: React.FC<RunGroupCardProps> = ({
|
|||||||
const snippetRef = React.useRef<SnippetAutocompleteHandle>(null);
|
const snippetRef = React.useRef<SnippetAutocompleteHandle>(null);
|
||||||
|
|
||||||
const handleAddModel = React.useCallback((model: ModelSelectionWithId) => {
|
const handleAddModel = React.useCallback((model: ModelSelectionWithId) => {
|
||||||
if (group.models.length >= MAX_MODELS_PER_GROUP) return;
|
|
||||||
onUpdate(group.id, { models: [...group.models, model] });
|
onUpdate(group.id, { models: [...group.models, model] });
|
||||||
}, [group.id, group.models, onUpdate]);
|
}, [group.id, group.models, onUpdate]);
|
||||||
|
|
||||||
@@ -987,7 +985,7 @@ const RunGroupCard: React.FC<RunGroupCardProps> = ({
|
|||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<FieldLabel
|
<FieldLabel
|
||||||
required
|
required
|
||||||
info={<InfoTip>{t('multirun.launcher.models.info', { max: MAX_MODELS_PER_GROUP })}</InfoTip>}
|
info={<InfoTip>{t('multirun.launcher.models.info')}</InfoTip>}
|
||||||
>
|
>
|
||||||
{t('multirun.launcher.models.label')}
|
{t('multirun.launcher.models.label')}
|
||||||
</FieldLabel>
|
</FieldLabel>
|
||||||
@@ -997,7 +995,6 @@ const RunGroupCard: React.FC<RunGroupCardProps> = ({
|
|||||||
onRemove={handleRemoveModel}
|
onRemove={handleRemoveModel}
|
||||||
onUpdate={handleUpdateModel}
|
onUpdate={handleUpdateModel}
|
||||||
minModels={1}
|
minModels={1}
|
||||||
maxModels={MAX_MODELS_PER_GROUP}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import { useI18n } from '@/lib/i18n';
|
|||||||
|
|
||||||
/** Max file size in bytes (10MB) */
|
/** Max file size in bytes (10MB) */
|
||||||
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
||||||
/** Max number of concurrent runs */
|
|
||||||
const MAX_MODELS = 5;
|
|
||||||
|
|
||||||
/** Attached file for agent manager */
|
/** Attached file for agent manager */
|
||||||
interface AttachedFile {
|
interface AttachedFile {
|
||||||
@@ -132,11 +130,8 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
|||||||
}, [projectRef]);
|
}, [projectRef]);
|
||||||
|
|
||||||
const handleAddModel = React.useCallback((model: ModelSelectionWithId) => {
|
const handleAddModel = React.useCallback((model: ModelSelectionWithId) => {
|
||||||
if (selectedModels.length >= MAX_MODELS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setSelectedModels((prev) => [...prev, model]);
|
setSelectedModels((prev) => [...prev, model]);
|
||||||
}, [selectedModels.length]);
|
}, []);
|
||||||
|
|
||||||
const handleRemoveModel = React.useCallback((index: number) => {
|
const handleRemoveModel = React.useCallback((index: number) => {
|
||||||
setSelectedModels((prev) => prev.filter((_, i) => i !== index));
|
setSelectedModels((prev) => prev.filter((_, i) => i !== index));
|
||||||
@@ -529,7 +524,6 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
|||||||
onUpdate={handleUpdateModel}
|
onUpdate={handleUpdateModel}
|
||||||
minModels={1}
|
minModels={1}
|
||||||
addButtonLabel={t('agentManager.empty.models.addModel')}
|
addButtonLabel={t('agentManager.empty.models.addModel')}
|
||||||
maxModels={5}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ export const dict = {
|
|||||||
'multirun.launcher.attachments.attach': 'Attach',
|
'multirun.launcher.attachments.attach': 'Attach',
|
||||||
'multirun.launcher.attachments.tooltip': 'Same files sent to all runs',
|
'multirun.launcher.attachments.tooltip': 'Same files sent to all runs',
|
||||||
'multirun.launcher.models.label': 'Models',
|
'multirun.launcher.models.label': 'Models',
|
||||||
'multirun.launcher.models.info': 'Select 2-{max} models. Same model can be added multiple times.',
|
'multirun.launcher.models.info': 'Select 2 or more models. Same model can be added multiple times.',
|
||||||
'multirun.launcher.toast.fileTooLarge': 'File "{fileName}" is too large (max 10MB)',
|
'multirun.launcher.toast.fileTooLarge': 'File "{fileName}" is too large (max 10MB)',
|
||||||
'multirun.launcher.toast.attachFailed': 'Failed to attach "{fileName}"',
|
'multirun.launcher.toast.attachFailed': 'Failed to attach "{fileName}"',
|
||||||
'multirun.launcher.toast.attachedSingle': 'Attached {count} file',
|
'multirun.launcher.toast.attachedSingle': 'Attached {count} file',
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"multirun.launcher.attachments.attach": "Adjuntar",
|
"multirun.launcher.attachments.attach": "Adjuntar",
|
||||||
"multirun.launcher.attachments.tooltip": "Archivos idénticos enviados a todas las ejecuciones",
|
"multirun.launcher.attachments.tooltip": "Archivos idénticos enviados a todas las ejecuciones",
|
||||||
"multirun.launcher.models.label": "Modelos",
|
"multirun.launcher.models.label": "Modelos",
|
||||||
"multirun.launcher.models.info": "Selecciona 2-{max} modelos. El mismo modelo puede añadirse varias veces.",
|
"multirun.launcher.models.info": "Selecciona 2 o más modelos. El mismo modelo puede añadirse varias veces.",
|
||||||
"multirun.launcher.toast.fileTooLarge": "El archivo \"{fileName}\" es demasiado grande (máximo 10MB)",
|
"multirun.launcher.toast.fileTooLarge": "El archivo \"{fileName}\" es demasiado grande (máximo 10MB)",
|
||||||
"multirun.launcher.toast.attachFailed": "No se pudo adjuntar \"{fileName}\"",
|
"multirun.launcher.toast.attachFailed": "No se pudo adjuntar \"{fileName}\"",
|
||||||
"multirun.launcher.toast.attachedSingle": "Archivo adjuntado ({count})",
|
"multirun.launcher.toast.attachedSingle": "Archivo adjuntado ({count})",
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export const dict = {
|
|||||||
'multirun.launcher.attachments.attach': 'Attacher',
|
'multirun.launcher.attachments.attach': 'Attacher',
|
||||||
'multirun.launcher.attachments.tooltip': 'Mêmes fichiers envoyés à toutes les exécutions',
|
'multirun.launcher.attachments.tooltip': 'Mêmes fichiers envoyés à toutes les exécutions',
|
||||||
'multirun.launcher.models.label': 'Modèles',
|
'multirun.launcher.models.label': 'Modèles',
|
||||||
'multirun.launcher.models.info': 'Sélectionnez les modèles 2-{max}. Le même modèle peut être ajouté plusieurs fois.',
|
'multirun.launcher.models.info': 'Sélectionnez 2 modèles ou plus. Le même modèle peut être ajouté plusieurs fois.',
|
||||||
'multirun.launcher.toast.fileTooLarge': 'Le fichier "{fileName}" est trop volumineux (max 10 Mo)',
|
'multirun.launcher.toast.fileTooLarge': 'Le fichier "{fileName}" est trop volumineux (max 10 Mo)',
|
||||||
'multirun.launcher.toast.attachFailed': 'Échec de la connexion de "{fileName}"',
|
'multirun.launcher.toast.attachFailed': 'Échec de la connexion de "{fileName}"',
|
||||||
'multirun.launcher.toast.attachedSingle': 'Fichier {count} joint',
|
'multirun.launcher.toast.attachedSingle': 'Fichier {count} joint',
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'multirun.launcher.attachments.attach': '첨부',
|
'multirun.launcher.attachments.attach': '첨부',
|
||||||
'multirun.launcher.attachments.tooltip': '같은 파일을 모든 실행에 보냅니다',
|
'multirun.launcher.attachments.tooltip': '같은 파일을 모든 실행에 보냅니다',
|
||||||
'multirun.launcher.models.label': '모델',
|
'multirun.launcher.models.label': '모델',
|
||||||
'multirun.launcher.models.info': '모델을 2~{max}개 선택하세요. 같은 모델을 여러 번 추가할 수 있습니다.',
|
'multirun.launcher.models.info': '모델을 2개 이상 선택하세요. 같은 모델을 여러 번 추가할 수 있습니다.',
|
||||||
'multirun.launcher.toast.fileTooLarge': '파일 "{fileName}"이 너무 큽니다(최대 10MB)',
|
'multirun.launcher.toast.fileTooLarge': '파일 "{fileName}"이 너무 큽니다(최대 10MB)',
|
||||||
'multirun.launcher.toast.attachFailed': '"{fileName}" 첨부 실패',
|
'multirun.launcher.toast.attachFailed': '"{fileName}" 첨부 실패',
|
||||||
'multirun.launcher.toast.attachedSingle': '파일 {count}개 첨부됨',
|
'multirun.launcher.toast.attachedSingle': '파일 {count}개 첨부됨',
|
||||||
|
|||||||
@@ -497,7 +497,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'multirun.launcher.attachments.attach': 'Dołącz',
|
'multirun.launcher.attachments.attach': 'Dołącz',
|
||||||
'multirun.launcher.attachments.tooltip': 'Te same pliki wysłane do wszystkich uruchomień',
|
'multirun.launcher.attachments.tooltip': 'Te same pliki wysłane do wszystkich uruchomień',
|
||||||
'multirun.launcher.models.label': 'Modele',
|
'multirun.launcher.models.label': 'Modele',
|
||||||
'multirun.launcher.models.info': 'Wybierz od 2 do {max} modeli. Ten sam model może być dodany wielokrotnie.',
|
'multirun.launcher.models.info': 'Wybierz 2 lub więcej modeli. Ten sam model może być dodany wielokrotnie.',
|
||||||
'multirun.launcher.toast.fileTooLarge': 'Plik "{fileName}" jest zbyt duży (max 10MB)',
|
'multirun.launcher.toast.fileTooLarge': 'Plik "{fileName}" jest zbyt duży (max 10MB)',
|
||||||
'multirun.launcher.toast.attachFailed': 'Nie udało się dołączyć "{fileName}"',
|
'multirun.launcher.toast.attachFailed': 'Nie udało się dołączyć "{fileName}"',
|
||||||
'multirun.launcher.toast.attachedSingle': 'Dołączono {count} plik',
|
'multirun.launcher.toast.attachedSingle': 'Dołączono {count} plik',
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"multirun.launcher.attachments.attach": "Anexar",
|
"multirun.launcher.attachments.attach": "Anexar",
|
||||||
"multirun.launcher.attachments.tooltip": "Arquivos idênticos enviados a todas as execuções",
|
"multirun.launcher.attachments.tooltip": "Arquivos idênticos enviados a todas as execuções",
|
||||||
"multirun.launcher.models.label": "Modelos",
|
"multirun.launcher.models.label": "Modelos",
|
||||||
"multirun.launcher.models.info": "Selecione 2-{max} modelos. O mesmo modelo pode ser adicionado várias vezes.",
|
"multirun.launcher.models.info": "Selecione 2 ou mais modelos. O mesmo modelo pode ser adicionado várias vezes.",
|
||||||
"multirun.launcher.toast.fileTooLarge": "O arquivo \"{fileName}\" é grande demais (máximo 10MB)",
|
"multirun.launcher.toast.fileTooLarge": "O arquivo \"{fileName}\" é grande demais (máximo 10MB)",
|
||||||
"multirun.launcher.toast.attachFailed": "Não foi possível anexar \"{fileName}\"",
|
"multirun.launcher.toast.attachFailed": "Não foi possível anexar \"{fileName}\"",
|
||||||
"multirun.launcher.toast.attachedSingle": "Arquivo anexado ({count})",
|
"multirun.launcher.toast.attachedSingle": "Arquivo anexado ({count})",
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"multirun.launcher.attachments.attach": "Прикріпити",
|
"multirun.launcher.attachments.attach": "Прикріпити",
|
||||||
"multirun.launcher.attachments.tooltip": "Ті самі файли буде надіслано в усі запуски",
|
"multirun.launcher.attachments.tooltip": "Ті самі файли буде надіслано в усі запуски",
|
||||||
"multirun.launcher.models.label": "Моделі",
|
"multirun.launcher.models.label": "Моделі",
|
||||||
"multirun.launcher.models.info": "Вибрати моделі 2-{max}. Ту саму модель можна додавати кілька разів.",
|
"multirun.launcher.models.info": "Вибрати 2 або більше моделей. Ту саму модель можна додавати кілька разів.",
|
||||||
"multirun.launcher.toast.fileTooLarge": "Файл \"{fileName}\" завеликий (макс. 10 МБ)",
|
"multirun.launcher.toast.fileTooLarge": "Файл \"{fileName}\" завеликий (макс. 10 МБ)",
|
||||||
"multirun.launcher.toast.attachFailed": "Не вдалося вкласти \"{fileName}\"",
|
"multirun.launcher.toast.attachFailed": "Не вдалося вкласти \"{fileName}\"",
|
||||||
"multirun.launcher.toast.attachedSingle": "Прикріплено файл: {count}",
|
"multirun.launcher.toast.attachedSingle": "Прикріплено файл: {count}",
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'multirun.launcher.attachments.attach': '附加',
|
'multirun.launcher.attachments.attach': '附加',
|
||||||
'multirun.launcher.attachments.tooltip': '相同文件会发送到所有运行',
|
'multirun.launcher.attachments.tooltip': '相同文件会发送到所有运行',
|
||||||
'multirun.launcher.models.label': '模型',
|
'multirun.launcher.models.label': '模型',
|
||||||
'multirun.launcher.models.info': '选择 2-{max} 个模型。同一模型可重复添加。',
|
'multirun.launcher.models.info': '选择 2 个或更多模型。同一模型可重复添加。',
|
||||||
'multirun.launcher.toast.fileTooLarge': '文件“{fileName}”过大(最大 10MB)',
|
'multirun.launcher.toast.fileTooLarge': '文件“{fileName}”过大(最大 10MB)',
|
||||||
'multirun.launcher.toast.attachFailed': '附加“{fileName}”失败',
|
'multirun.launcher.toast.attachFailed': '附加“{fileName}”失败',
|
||||||
'multirun.launcher.toast.attachedSingle': '已附加 {count} 个文件',
|
'multirun.launcher.toast.attachedSingle': '已附加 {count} 个文件',
|
||||||
|
|||||||
@@ -383,7 +383,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'multirun.launcher.attachments.attach': '附加',
|
'multirun.launcher.attachments.attach': '附加',
|
||||||
'multirun.launcher.attachments.tooltip': '相同檔案會傳送到所有執行',
|
'multirun.launcher.attachments.tooltip': '相同檔案會傳送到所有執行',
|
||||||
'multirun.launcher.models.label': '模型',
|
'multirun.launcher.models.label': '模型',
|
||||||
'multirun.launcher.models.info': '選擇 2-{max} 個模型。同一模型可重複加入。',
|
'multirun.launcher.models.info': '選擇 2 個或更多模型。同一模型可重複加入。',
|
||||||
'multirun.launcher.toast.fileTooLarge': '檔案「{fileName}」過大(最大 10MB)',
|
'multirun.launcher.toast.fileTooLarge': '檔案「{fileName}」過大(最大 10MB)',
|
||||||
'multirun.launcher.toast.attachFailed': '附加「{fileName}」失敗',
|
'multirun.launcher.toast.attachFailed': '附加「{fileName}」失敗',
|
||||||
'multirun.launcher.toast.attachedSingle': '已附加 {count} 個檔案',
|
'multirun.launcher.toast.attachedSingle': '已附加 {count} 個檔案',
|
||||||
|
|||||||
@@ -226,4 +226,20 @@ describe('useMultiRunStore', () => {
|
|||||||
'createSession:/repo-worktrees/fix-thing',
|
'createSession:/repo-worktrees/fix-thing',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('accepts more than 5 models per group without a "maximum 5 models" error', async () => {
|
||||||
|
const models = Array.from({ length: 6 }, (_, i) => ({
|
||||||
|
providerID: 'anthropic',
|
||||||
|
modelID: `claude-sonnet-4-5-${i}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const result = await useMultiRunStore.getState().createMultiRun({
|
||||||
|
name: 'Many models',
|
||||||
|
isolateRuns: false,
|
||||||
|
groups: [{ prompt: 'Fix it', models }],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(useMultiRunStore.getState().error).toBeNull();
|
||||||
|
expect(result?.sessionIds).toHaveLength(6);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -138,10 +138,6 @@ export const useMultiRunStore = create<MultiRunStore>()(
|
|||||||
set({ error: `Group ${gi + 1}: select at least 1 model` });
|
set({ error: `Group ${gi + 1}: select at least 1 model` });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (groups[gi].models.length > 5) {
|
|
||||||
set({ error: `Group ${gi + 1}: maximum 5 models allowed` });
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set({ isLoading: true, error: null });
|
set({ isLoading: true, error: null });
|
||||||
|
|||||||
Reference in New Issue
Block a user