fix(wiki): preserve agent id tool inputs

This commit is contained in:
matevip 2026-08-13 03:56:03 -04:00
parent b1f3332ae1
commit 7bf18e6af8
4 changed files with 120 additions and 45 deletions

View File

@ -154,9 +154,10 @@ public class WikiTool {
- boundToAgent true if the KB is explicitly bound to this agent
""")
public String wiki_list_kbs(
@ToolParam(description = "Agent ID") Long agentId) {
List<WikiKnowledgeBaseEntity> kbs = kbService.listByAgentId(agentId);
WikiKnowledgeBaseEntity primary = kbService.resolvePrimaryKb(agentId);
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId) {
Long parsedAgentId = parseAgentId(agentId);
List<WikiKnowledgeBaseEntity> kbs = kbService.listByAgentId(parsedAgentId);
WikiKnowledgeBaseEntity primary = kbService.resolvePrimaryKb(parsedAgentId);
Long primaryId = primary == null ? null : primary.getId();
JSONArray arr = new JSONArray();
@ -195,7 +196,7 @@ public class WikiTool {
consult that page first call this tool with the bare slug before answering.
""")
public String wiki_read_page(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug") String slug,
@ToolParam(description = "Max characters to return (null = full page)", required = false) Integer maxChars,
@ToolParam(description = "Section heading to extract (null = all sections)", required = false) String sectionHeading,
@ -247,7 +248,7 @@ public class WikiTool {
Without query returns all pages (use only for small KBs).
""")
public String wiki_list_pages(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Title keyword filter (optional)", required = false) String query,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ -307,7 +308,7 @@ public class WikiTool {
When using wiki information in your answer, always cite the source page title.
""")
public String wiki_search_pages(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Search query") String query,
@ToolParam(description = "Mode: keyword|semantic|hybrid (default: hybrid)", required = false) String mode,
@ToolParam(description = "Max results (default 5, max 20)", required = false) Integer topK,
@ -374,7 +375,7 @@ public class WikiTool {
When using retrieved content in your answer, cite the source page title shown in each result.
""")
public String wiki_semantic_search(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Natural language query") String query,
@ToolParam(description = "Max results (default 5)", required = false) Integer topK,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ -441,7 +442,7 @@ public class WikiTool {
Returns file names, types, and paths of the original documents.
""")
public String wiki_trace_source(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ -474,7 +475,7 @@ public class WikiTool {
Content should be Markdown. Slug is auto-generated from title.
""")
public String wiki_create_page(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page title") String title,
@ToolParam(description = "Page content (Markdown)") String content,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ -561,7 +562,7 @@ public class WikiTool {
Set slug to control the page slug; otherwise it's derived from the topic.
""")
public String wiki_compile_page(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Topic to compile a page about (natural language)") String topic,
@ToolParam(description = "Optional explicit slug for the page", required = false) String slug,
@ToolParam(description = "Max evidence chunks (default 8, max 20)", required = false) Integer maxEvidenceChunks,
@ -618,7 +619,7 @@ public class WikiTool {
page; protected/system pages can still be read explicitly here.
""")
public String wiki_read_many(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Comma-separated slugs (max 10)") String slugs,
@ToolParam(description = "Max chars returned per page (default 2000, max 8000)", required = false) Integer maxCharsPerPage,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ -670,7 +671,7 @@ public class WikiTool {
System pages (overview / log) cannot be archived.
""")
public String wiki_archive_page(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug to archive") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ -682,14 +683,14 @@ public class WikiTool {
list / search / related results again. No-op when the page wasn't archived.
""")
public String wiki_unarchive_page(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug to unarchive") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
return setArchivedTool(agentId, slug, false, "unarchived", kbName, kbId);
}
private String setArchivedTool(Long agentId, String slug, boolean archive, String verb, String kbName, Long kbId) {
private String setArchivedTool(String agentId, String slug, boolean archive, String verb, String kbName, Long kbId) {
if (slug == null || slug.isBlank()) return error("slug is required");
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
if (kbRes.hasError()) return kbRes.errorJson();
@ -725,7 +726,7 @@ public class WikiTool {
Delete an AI-generated wiki page. Cannot delete manually curated pages.
""")
public String wiki_delete_page(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug to delete") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ -781,7 +782,7 @@ public class WikiTool {
semantic similarity). More reliable than keyword search for discovering connected knowledge.
""")
public String wiki_related_pages(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug") String slug,
@ToolParam(description = "Max results (default 5, max 10)", required = false) Integer topK,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ -819,7 +820,7 @@ public class WikiTool {
Explain why two wiki pages are related. Returns signal breakdown with scores.
""")
public String wiki_explain_relation(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "First page slug") String slugA,
@ToolParam(description = "Second page slug") String slugB,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ -854,7 +855,7 @@ public class WikiTool {
Does NOT regenerate content only adds [[wikilink]] cross-references.
""")
public String wiki_enrich_page(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Page slug") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ -898,7 +899,7 @@ public class WikiTool {
is re-derived from the new content unless you pass one explicitly.
""")
public String wiki_update_page(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Slug of the page to update (from wiki_list_pages / wiki_read_page)") String slug,
@ToolParam(description = "New full Markdown content for the page body") String content,
@ToolParam(description = "New one-line summary (optional; omit to auto-derive from content)", required = false) String summary,
@ -944,7 +945,7 @@ public class WikiTool {
relying on experience/analysis pages.
""")
public String wiki_stale_pages(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ -980,7 +981,7 @@ public class WikiTool {
human title, and a description of what the prompt produces.
""")
public String wiki_list_transformations(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
KbResolution kbRes = resolveKb(agentId, kbName, kbId);
@ -1010,7 +1011,7 @@ public class WikiTool {
The run is also persisted so the result is visible in the wiki UI.
""")
public String wiki_apply_transformation(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Transformation name (from wiki_list_transformations)") String name,
@ToolParam(description = "Raw material ID to run the transformation against") Long rawId,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ -1065,7 +1066,7 @@ public class WikiTool {
is persisted in the wiki UI; pass slug (not page id) for convenience.
""")
public String wiki_apply_transformation_to_page(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Transformation name (from wiki_list_transformations)") String name,
@ToolParam(description = "Source wiki page slug to run the transformation against") String slug,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ -1127,7 +1128,7 @@ public class WikiTool {
account). Idempotent re-running upserts the same slug.
""")
public String wiki_aggregate_transformation(
@ToolParam(description = "Agent ID") Long agentId,
@ToolParam(description = "Agent ID. Must be passed as a string to preserve large integer precision") String agentId,
@ToolParam(description = "Transformation name (from wiki_list_transformations)") String name,
@ToolParam(description = "Target knowledge base name (from wiki_list_kbs). Omit to use the agent's primary KB; switch to `kbId` when two KBs share the name.", required = false) String kbName,
@ToolParam(description = "Numeric KB id from wiki_list_kbs. Use when `kbName` returns an ambiguous-name error.", required = false) Long kbId) {
@ -1183,6 +1184,18 @@ public class WikiTool {
return resolveKbId(agentId, null, null);
}
private Long parseAgentId(String agentId) {
String trimmed = agentId != null ? agentId.trim() : "";
if (trimmed.isEmpty()) {
throw new IllegalArgumentException("agentId is required");
}
try {
return Long.parseLong(trimmed);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("agentId must be a numeric string");
}
}
/**
* Outcome of resolving a KB for a tool call. Exactly one of
* {@code kbId} / {@code errorJson} is non-null:
@ -1210,6 +1223,10 @@ public class WikiTool {
return pageTypePermissionService.resolve(agentId, kbId);
}
private WikiPageTypePermissionService.Access pageTypeAccess(String agentId, Long kbId) {
return pageTypeAccess(parseAgentId(agentId), kbId);
}
/** Whether the resolved access permits reading {@code page}. Null-safe. */
private boolean canRead(WikiPageTypePermissionService.Access access, WikiPageEntity page) {
return access == null || page == null || access.canRead(page.getPageType());
@ -1273,6 +1290,11 @@ public class WikiTool {
};
}
private String checkWrite(String agentId, Long kbId, String pageType,
WikiPageTypePermissionService.WriteOp op) {
return checkWrite(parseAgentId(agentId), kbId, pageType, op);
}
/**
* Record a pending approval for an {@code APPROVAL_REQUIRED} wiki write,
* keyed to the current conversation via {@link ChatOriginHolder}. Best-effort:
@ -1390,6 +1412,10 @@ public class WikiTool {
return KbResolution.ok(primary.getId());
}
private KbResolution resolveKb(String agentId, String kbName, Long kbId) {
return resolveKb(parseAgentId(agentId), kbName, kbId);
}
/**
* Legacy 2-arg routing kept for the tool methods that haven't been
* widened to accept {@code kbId} yet. Always returns null when the

View File

@ -0,0 +1,47 @@
package vip.mate.wiki.tool;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import vip.mate.wiki.service.HybridRetriever;
import vip.mate.wiki.service.WikiKnowledgeBaseService;
import vip.mate.wiki.service.WikiPageService;
import vip.mate.wiki.service.WikiPageTypePermissionService;
import vip.mate.wiki.service.WikiRawMaterialService;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
class WikiToolIdSchemaTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Test
@DisplayName("wiki tools publish agentId as a string parameter so LLM tool calls preserve precision")
void wikiToolAgentIdSchemasAreString() throws Exception {
WikiTool tool = new WikiTool(
mock(WikiPageService.class),
mock(WikiKnowledgeBaseService.class),
mock(WikiRawMaterialService.class),
mock(HybridRetriever.class),
new ObjectMapper(),
mock(WikiPageTypePermissionService.class));
int checked = 0;
for (ToolCallback callback : ToolCallbacks.from(tool)) {
JsonNode root = MAPPER.readTree(callback.getToolDefinition().inputSchema());
JsonNode agentId = root.at("/properties/agentId/type");
if (!agentId.isMissingNode()) {
assertThat(agentId.asText())
.as(callback.getToolDefinition().name())
.isEqualTo("string");
checked++;
}
}
assertThat(checked).isGreaterThan(0);
}
}

View File

@ -51,6 +51,7 @@ import static org.mockito.Mockito.when;
class WikiToolKbNameRoutingTest {
private static final Long AGENT = 7L;
private static final String AGENT_PARAM = String.valueOf(AGENT);
private static final long PRIMARY_KB = 100L;
private static final long OTHER_KB = 200L;
private static final long DUP_BOUND_KB = 300L;
@ -109,7 +110,7 @@ class WikiToolKbNameRoutingTest {
wirePages();
when(kbService.resolvePrimaryKb(AGENT)).thenReturn(kb(PRIMARY_KB, "Primary", AGENT));
String json = tool.wiki_list_pages(AGENT, null, null, null);
String json = tool.wiki_list_pages(AGENT_PARAM, null, null, null);
JSONObject obj = JSONUtil.parseObj(json);
JSONArray pages = obj.getJSONArray("pages");
@ -126,7 +127,7 @@ class WikiToolKbNameRoutingTest {
when(kbService.resolvePrimaryKb(AGENT)).thenReturn(kb(PRIMARY_KB, "Primary", AGENT));
when(kbService.findAllByName(AGENT, "Other")).thenReturn(List.of(kb(OTHER_KB, "Other", null)));
String json = tool.wiki_list_pages(AGENT, null, "Other", null);
String json = tool.wiki_list_pages(AGENT_PARAM, null, "Other", null);
JSONObject obj = JSONUtil.parseObj(json);
JSONArray pages = obj.getJSONArray("pages");
@ -141,7 +142,7 @@ class WikiToolKbNameRoutingTest {
when(kbService.resolvePrimaryKb(AGENT)).thenReturn(kb(PRIMARY_KB, "Primary", AGENT));
when(kbService.findVisibleById(AGENT, OTHER_KB)).thenReturn(kb(OTHER_KB, "Other", null));
String json = tool.wiki_list_pages(AGENT, null, null, OTHER_KB);
String json = tool.wiki_list_pages(AGENT_PARAM, null, null, OTHER_KB);
JSONObject obj = JSONUtil.parseObj(json);
assertThat(obj.getJSONArray("pages").getJSONObject(0).getStr("slug"))
@ -156,7 +157,7 @@ class WikiToolKbNameRoutingTest {
// Deliberately do NOT stub findAllByName if the tool consulted
// kbName at all (or fell back to primary), the call would NPE.
String json = tool.wiki_list_pages(AGENT, null, "anything", OTHER_KB);
String json = tool.wiki_list_pages(AGENT_PARAM, null, "anything", OTHER_KB);
JSONObject obj = JSONUtil.parseObj(json);
assertThat(obj.getJSONArray("pages").getJSONObject(0).getStr("slug"))
.isEqualTo("other-only-slug");
@ -171,7 +172,7 @@ class WikiToolKbNameRoutingTest {
// Primary still mockable; the routing must NOT silently fall through.
when(kbService.resolvePrimaryKb(AGENT)).thenReturn(kb(PRIMARY_KB, "Primary", AGENT));
String json = tool.wiki_list_pages(AGENT, null, "Bogus", null);
String json = tool.wiki_list_pages(AGENT_PARAM, null, "Bogus", null);
JSONObject obj = JSONUtil.parseObj(json);
assertThat(obj.getStr("error"))
@ -188,7 +189,7 @@ class WikiToolKbNameRoutingTest {
kb(DUP_SHARED_KB, "Docs", null)));
when(kbService.resolvePrimaryKb(AGENT)).thenReturn(kb(PRIMARY_KB, "Primary", AGENT));
String json = tool.wiki_list_pages(AGENT, null, "Docs", null);
String json = tool.wiki_list_pages(AGENT_PARAM, null, "Docs", null);
JSONObject obj = JSONUtil.parseObj(json);
// Error must be ambiguity-flavoured so the LLM knows to retry with kbId.
@ -217,7 +218,7 @@ class WikiToolKbNameRoutingTest {
// would return null and surface a spurious "kbId=0 not visible" error,
// which is exactly the production regression this test prevents.
String json = tool.wiki_list_pages(AGENT, null, null, 0L);
String json = tool.wiki_list_pages(AGENT_PARAM, null, null, 0L);
JSONObject obj = JSONUtil.parseObj(json);
assertThat(obj.getStr("error"))
@ -234,7 +235,7 @@ class WikiToolKbNameRoutingTest {
when(kbService.findVisibleById(AGENT, 99999L)).thenReturn(null);
when(kbService.resolvePrimaryKb(AGENT)).thenReturn(kb(PRIMARY_KB, "Primary", AGENT));
String json = tool.wiki_list_pages(AGENT, null, null, 99999L);
String json = tool.wiki_list_pages(AGENT_PARAM, null, null, 99999L);
JSONObject obj = JSONUtil.parseObj(json);
assertThat(obj.getStr("error"))
@ -248,7 +249,7 @@ class WikiToolKbNameRoutingTest {
void noResolvableKbReturnsLegacyError() {
when(kbService.resolvePrimaryKb(AGENT)).thenReturn(null);
String json = tool.wiki_list_pages(AGENT, null, null, null);
String json = tool.wiki_list_pages(AGENT_PARAM, null, null, null);
JSONObject obj = JSONUtil.parseObj(json);
assertThat(obj.getStr("error")).contains("No wiki knowledge base found");
@ -264,7 +265,7 @@ class WikiToolKbNameRoutingTest {
kb(PRIMARY_KB, "Primary", AGENT)));
when(kbService.resolvePrimaryKb(AGENT)).thenReturn(kb(PRIMARY_KB, "Primary", AGENT));
String json = tool.wiki_list_kbs(AGENT);
String json = tool.wiki_list_kbs(AGENT_PARAM);
JSONObject obj = JSONUtil.parseObj(json);
assertThat(obj.getInt("kbCount")).isEqualTo(2);

View File

@ -33,6 +33,7 @@ import static org.mockito.Mockito.when;
class WikiToolPermissionTest {
private static final long AGENT = 11L;
private static final String AGENT_PARAM = String.valueOf(AGENT);
private static final long KB = 7L;
private record Harness(WikiTool tool, WikiPageService pageService,
@ -90,7 +91,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("*", 0, 0, 0, 0, "deny")));
when(h.pageService().getBySlug(KB, "secret")).thenReturn(page("secret", "analysis"));
String out = h.tool().wiki_read_page(AGENT, "secret", null, null, null, KB);
String out = h.tool().wiki_read_page(AGENT_PARAM, "secret", null, null, null, KB);
assertTrue(out.contains("Page not found"), out);
}
@ -100,7 +101,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("*", 1, 0, 0, 0, "deny")));
when(h.pageService().getBySlug(KB, "ok")).thenReturn(page("ok", "concept"));
String out = h.tool().wiki_read_page(AGENT, "ok", null, null, null, KB);
String out = h.tool().wiki_read_page(AGENT_PARAM, "ok", null, null, null, KB);
assertTrue(out.contains("\"content\""), out);
assertFalse(out.contains("Page not found"), out);
@ -112,7 +113,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("concept", 1, 0, 0, 0, "deny")));
when(h.pageService().getBySlug(KB, "p")).thenReturn(page("p", "concept"));
String out = h.tool().wiki_delete_page(AGENT, "p", null, KB);
String out = h.tool().wiki_delete_page(AGENT_PARAM, "p", null, KB);
assertTrue(out.contains("Not permitted"), out);
verify(h.pageService(), never()).delete(anyLong(), any());
@ -123,7 +124,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("concept", 1, 0, 0, 1, "approval_required")));
when(h.pageService().getBySlug(KB, "p")).thenReturn(page("p", "concept"));
String out = h.tool().wiki_delete_page(AGENT, "p", null, KB);
String out = h.tool().wiki_delete_page(AGENT_PARAM, "p", null, KB);
assertTrue(out.contains("Approval required"), out);
verify(h.pageService(), never()).delete(anyLong(), any());
@ -134,7 +135,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("concept", 1, 1, 1, 1, "allow")));
when(h.pageService().getBySlug(KB, "p")).thenReturn(page("p", "concept"));
String out = h.tool().wiki_delete_page(AGENT, "p", null, KB);
String out = h.tool().wiki_delete_page(AGENT_PARAM, "p", null, KB);
assertTrue(out.contains("\"ok\":true"), out);
verify(h.pageService(), times(1)).delete(eq(KB), eq("p"));
@ -145,7 +146,7 @@ class WikiToolPermissionTest {
// a row exists for 'episode' only KB is gated, wildcard create not granted
Harness h = harness(List.of(row("episode", 1, 1, 1, 1, "allow")));
String out = h.tool().wiki_create_page(AGENT, "New Page", "content here", null, KB);
String out = h.tool().wiki_create_page(AGENT_PARAM, "New Page", "content here", null, KB);
assertTrue(out.contains("Not permitted"), out);
verify(h.pageService(), never()).createPage(anyLong(), any(), any(), any(), any(), any());
@ -161,7 +162,7 @@ class WikiToolPermissionTest {
updated.setVersion(2);
when(h.pageService().updatePageManually(eq(KB), eq("p"), any(), any())).thenReturn(updated);
String out = h.tool().wiki_update_page(AGENT, "p", "new body", null, null, KB);
String out = h.tool().wiki_update_page(AGENT_PARAM, "p", "new body", null, null, KB);
assertTrue(out.contains("\"ok\":true"), out);
assertTrue(out.contains("updated in place"), out);
@ -177,7 +178,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("concept", 1, 1, 0, 0, "allow")));
when(h.pageService().getBySlug(KB, "p")).thenReturn(page("p", "concept"));
String out = h.tool().wiki_update_page(AGENT, "p", "new body", null, null, KB);
String out = h.tool().wiki_update_page(AGENT_PARAM, "p", "new body", null, null, KB);
assertTrue(out.contains("Not permitted"), out);
verify(h.pageService(), never()).updatePageManually(anyLong(), any(), any(), any());
@ -188,7 +189,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of(row("*", 1, 1, 1, 1, "allow")));
when(h.pageService().getBySlug(KB, "ghost")).thenReturn(null);
String out = h.tool().wiki_update_page(AGENT, "ghost", "body", null, null, KB);
String out = h.tool().wiki_update_page(AGENT_PARAM, "ghost", "body", null, null, KB);
assertTrue(out.contains("Page not found"), out);
verify(h.pageService(), never()).updatePageManually(anyLong(), any(), any(), any());
@ -212,7 +213,7 @@ class WikiToolPermissionTest {
WikiPageEntity staleHidden = stalePage("classified", "secret", "{\"reason\":\"x\"}");
when(h.pageService().listByKbId(KB)).thenReturn(List.of(fresh, staleOk, staleHidden));
String out = h.tool().wiki_stale_pages(AGENT, null, KB);
String out = h.tool().wiki_stale_pages(AGENT_PARAM, null, KB);
assertTrue(out.contains("\"staleCount\":1"), out);
assertTrue(out.contains("aged"), out);
@ -225,7 +226,7 @@ class WikiToolPermissionTest {
Harness h = harness(List.of());
when(h.pageService().listByKbId(KB)).thenReturn(List.of(page("a", "concept"), page("b", "episode")));
String out = h.tool().wiki_stale_pages(AGENT, null, KB);
String out = h.tool().wiki_stale_pages(AGENT_PARAM, null, KB);
assertTrue(out.contains("\"staleCount\":0"), out);
}