diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiKnowledgeBaseService.java b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiKnowledgeBaseService.java index 0e4e34a2..1c8aaa6a 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiKnowledgeBaseService.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiKnowledgeBaseService.java @@ -127,13 +127,22 @@ public class WikiKnowledgeBaseService { * KB just drops out). An agent with no scope rows stays workspace-wide, * preserving the pre-scoping behavior for every existing agent. *

+ * An agent flagged {@code wiki_disabled=true} sees no KBs at all — the + * opt-out toggle wins over both workspace visibility and any leftover + * binding rows (the UI clears the rows when the flag is set, so the + * zero-row state must not fall through to "unrestricted"). + *

* This is the single choke point for KB access: {@code wiki_list_kbs}, - * {@link #findVisibleById}, {@link #findAllByName} and - * {@link #resolvePrimaryKb} all read through here, so narrowing it scopes - * every wiki tool at once. + * {@link #findVisibleById}, {@link #findAllByName}, + * {@link #resolvePrimaryKb}, the system-prompt wiki context, and the + * per-turn relevant-page injection all read through here, so narrowing + * it scopes every wiki surface at once. */ public List listByAgentId(Long agentId) { AgentEntity agent = getAgentOrNull(agentId); + if (agent != null && Boolean.TRUE.equals(agent.getWikiDisabled())) { + return List.of(); + } List workspaceKbs = (agent == null || agent.getWorkspaceId() == null) ? listAll() : listByWorkspace(agent.getWorkspaceId()); diff --git a/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiKnowledgeBaseServiceTest.java b/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiKnowledgeBaseServiceTest.java index 7be62c78..552ed3d5 100644 --- a/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiKnowledgeBaseServiceTest.java +++ b/mateclaw-server/src/test/java/vip/mate/wiki/service/WikiKnowledgeBaseServiceTest.java @@ -15,6 +15,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -308,4 +309,38 @@ class WikiKnowledgeBaseServiceTest { assertThat(service.resolvePrimaryKb(7L).getId()).isEqualTo(300L); } + + // ==================== wiki_disabled opt-out (issue #551) ==================== + // + // The "use no knowledge base" toggle sets mate_agent.wiki_disabled=true + // and clears the binding rows. Zero rows normally means "unrestricted", + // so the flag must short-circuit BEFORE the scope fallback — otherwise + // the opted-out agent sees every workspace KB again and the system + // prompt keeps injecting wiki content. + + @Test + @DisplayName("wiki_disabled hides every KB: list, primary, and by-id lookups all come back empty") + void wikiDisabledHidesAllKbs() { + AgentEntity optedOut = agent(7L, 1L, 100L); + optedOut.setWikiDisabled(true); + when(agentMapper.selectById(7L)).thenReturn(optedOut); + + assertThat(service.listByAgentId(7L)).isEmpty(); + assertThat(service.resolvePrimaryKb(7L)).isNull(); + assertThat(service.findVisibleById(7L, 100L)).isNull(); + verify(kbMapper, never()).selectList(any()); + } + + @Test + @DisplayName("wiki_disabled wins over leftover binding rows") + void wikiDisabledWinsOverLeftoverBindings() { + bindScope(100L); + AgentEntity optedOut = agent(7L, 1L, 100L); + optedOut.setWikiDisabled(true); + when(agentMapper.selectById(7L)).thenReturn(optedOut); + when(kbMapper.selectList(any())).thenReturn(List.of( + kb(100L, null, 1L, "Bound KB"))); + + assertThat(service.listByAgentId(7L)).isEmpty(); + } }