From 082b3b0f888e287ab1e8e941fe7c746f9010d45f Mon Sep 17 00:00:00 2001 From: matevip Date: Tue, 3 Nov 2026 02:27:47 -0500 Subject: [PATCH] fix(agent): raise default iteration budget and explain capped runs (#674) --- .../java/vip/mate/agent/AgentService.java | 5 ++ .../AgentServiceIterationBudgetTest.java | 54 +++++++++++++++++++ .../src/components/chat/MessageBubble.vue | 15 ++++++ .../MessageBubble.stopIndicator.test.ts | 53 ++++++++++++++++-- mateclaw-ui/src/i18n/locales/en-US.ts | 2 + mateclaw-ui/src/i18n/locales/zh-CN.ts | 2 + mateclaw-ui/src/views/AgentCreateWizard.vue | 2 +- mateclaw-ui/src/views/Agents.vue | 2 +- .../AgentCreateWizard.iterationBudget.test.ts | 50 +++++++++++++++++ 9 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 mateclaw-server/src/test/java/vip/mate/agent/AgentServiceIterationBudgetTest.java create mode 100644 mateclaw-ui/src/views/__tests__/AgentCreateWizard.iterationBudget.test.ts diff --git a/mateclaw-server/src/main/java/vip/mate/agent/AgentService.java b/mateclaw-server/src/main/java/vip/mate/agent/AgentService.java index 8c59deda..be92acb7 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/AgentService.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/AgentService.java @@ -146,6 +146,11 @@ public class AgentService { public AgentEntity createAgent(AgentEntity agent) { agent.setEnabled(true); + // Match the runtime fallback before insert; older schemas default to 10. + // Explicit budgets (including 0 for no soft cap) remain user-controlled. + if (agent.getMaxIterations() == null) { + agent.setMaxIterations(100); + } if (agent.getAgentType() == null) { agent.setAgentType("react"); } diff --git a/mateclaw-server/src/test/java/vip/mate/agent/AgentServiceIterationBudgetTest.java b/mateclaw-server/src/test/java/vip/mate/agent/AgentServiceIterationBudgetTest.java new file mode 100644 index 00000000..fe21d893 --- /dev/null +++ b/mateclaw-server/src/test/java/vip/mate/agent/AgentServiceIterationBudgetTest.java @@ -0,0 +1,54 @@ +package vip.mate.agent; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import vip.mate.agent.model.AgentEntity; +import vip.mate.agent.repository.AgentMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +class AgentServiceIterationBudgetTest { + private final AgentMapper mapper = mock(AgentMapper.class); + private final AgentService service = new AgentService(mapper, null, null, null, null, null, null); + + @Test + void omittedBudgetIsStoredAsOneHundredInsteadOfUsingDatabaseDefault() { + AgentEntity agent = new AgentEntity(); + agent.setName("Research assistant"); + doAnswer(invocation -> { + AgentEntity inserted = invocation.getArgument(0); + assertEquals(100, inserted.getMaxIterations()); + return 1; + }).when(mapper).insert(any(AgentEntity.class)); + + assertEquals(100, service.createAgent(agent).getMaxIterations()); + verify(mapper).insert(agent); + } + + @ParameterizedTest + @ValueSource(ints = {0, 10, 150}) + void explicitCreationBudgetIsPreserved(int budget) { + AgentEntity agent = new AgentEntity(); + agent.setName("Custom assistant"); + agent.setMaxIterations(budget); + assertEquals(budget, service.createAgent(agent).getMaxIterations()); + verify(mapper).insert(agent); + } + + @Test + void editingExistingAgentRetainsItsSmallBudget() { + AgentEntity agent = new AgentEntity(); + agent.setId(1L); + agent.setName("Existing assistant"); + agent.setMaxIterations(10); + agent.setDescription("Updated description"); + when(mapper.selectById(1L)).thenReturn(agent); + + service.updateAgent(agent); + + assertEquals(10, agent.getMaxIterations()); + verify(mapper).updateById(agent); + } +} diff --git a/mateclaw-ui/src/components/chat/MessageBubble.vue b/mateclaw-ui/src/components/chat/MessageBubble.vue index 5a2a8f41..b416e6f4 100644 --- a/mateclaw-ui/src/components/chat/MessageBubble.vue +++ b/mateclaw-ui/src/components/chat/MessageBubble.vue @@ -263,6 +263,16 @@ + +
+
+ + {{ $t('chat.iterationLimitTitle') }} +
+

{{ $t('chat.iterationLimitDescription') }}

+
+