diff --git a/README.md b/README.md
index d30aaea..bedafbf 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ The current `golemcore/*` modules in this repository are:
| Plugin | Purpose |
| --- | --- |
| `golemcore/brave-search` | Brave Search web search tool plugin with API-key backed queries. |
-| `golemcore/brain` | GolemCore Brain wiki tool plugin for CRUD operations and optional dynamic relevance search. |
+| `golemcore/brain` | GolemCore Brain wiki tool plugin for CRUD operations, hybrid search, intellisearch, and reindexing. |
| `golemcore/browser` | Playwright-backed browser automation tool with screenshot support. |
| `golemcore/browserless` | Browserless smart scrape plugin for rendered HTML, markdown, and link extraction. |
| `golemcore/elevenlabs` | ElevenLabs-backed STT/TTS provider plugin. |
diff --git a/golemcore/brain/plugin.yaml b/golemcore/brain/plugin.yaml
index d438778..fd281f6 100644
--- a/golemcore/brain/plugin.yaml
+++ b/golemcore/brain/plugin.yaml
@@ -1,11 +1,11 @@
id: golemcore/brain
provider: golemcore
name: brain
-version: 1.0.2
+version: 1.0.3
pluginApiVersion: 1
engineVersion: ">=0.0.0 <2.0.0"
entrypoint: me.golemcore.plugins.golemcore.brain.BrainPluginBootstrap
-description: "GolemCore Brain wiki plugin for CRUD operations and optional dynamic relevance search."
+description: "GolemCore Brain wiki plugin for CRUD, section-level patching, atomic transactions, link-graph insights, top-read tracking, hybrid search, intellisearch, and reindexing."
sourceUrl: "https://github.com/alexk-dev/golemcore-plugins/tree/main/golemcore/brain"
license: "Apache-2.0"
maintainers:
diff --git a/golemcore/brain/pom.xml b/golemcore/brain/pom.xml
index f2ccbfb..96fd4bf 100644
--- a/golemcore/brain/pom.xml
+++ b/golemcore/brain/pom.xml
@@ -11,7 +11,7 @@
../../pom.xml
- 1.0.2
+ 1.0.3
golemcore-brain-plugin
golemcore/brain
GolemCore Brain wiki tool plugin for GolemCore
diff --git a/golemcore/brain/src/main/java/me/golemcore/plugins/golemcore/brain/BrainPluginSettingsContributor.java b/golemcore/brain/src/main/java/me/golemcore/plugins/golemcore/brain/BrainPluginSettingsContributor.java
index 597ebbd..5a8e22a 100644
--- a/golemcore/brain/src/main/java/me/golemcore/plugins/golemcore/brain/BrainPluginSettingsContributor.java
+++ b/golemcore/brain/src/main/java/me/golemcore/plugins/golemcore/brain/BrainPluginSettingsContributor.java
@@ -59,7 +59,7 @@ public PluginSettingsSection getSection(String sectionKey) {
values.put("allowWrite", Boolean.TRUE.equals(config.getAllowWrite()));
return PluginSettingsSection.builder()
.title("GolemCore Brain")
- .description("Configure the Brain wiki API tool and optional dynamic relevance endpoint.")
+ .description("Configure the Brain wiki API tool and optional dynamic intellisearch endpoint.")
.fields(List.of(
PluginSettingsField.builder()
.key("enabled")
@@ -108,8 +108,8 @@ public PluginSettingsSection getSection(String sectionKey) {
PluginSettingsField.builder()
.key("allowWrite")
.type("boolean")
- .label("Allow Write Operations")
- .description("Allow create, update, delete, ensure, move, and copy operations.")
+ .label("Allow Mutating Operations")
+ .description("Allow page mutations and search reindex requests.")
.build()))
.values(values)
.build();
diff --git a/golemcore/brain/src/main/java/me/golemcore/plugins/golemcore/brain/BrainService.java b/golemcore/brain/src/main/java/me/golemcore/plugins/golemcore/brain/BrainService.java
index 8ade7a5..b72e0e4 100644
--- a/golemcore/brain/src/main/java/me/golemcore/plugins/golemcore/brain/BrainService.java
+++ b/golemcore/brain/src/main/java/me/golemcore/plugins/golemcore/brain/BrainService.java
@@ -27,6 +27,9 @@ public class BrainService {
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private static final int DEFAULT_SEARCH_LIMIT = 5;
+ private static final String SEARCH_MODE_AUTO = "auto";
+ private static final String SEARCH_MODE_FTS = "fts";
+ private static final String SEARCH_MODE_HYBRID = "hybrid";
private static final Pattern SLUG_SEPARATOR_PATTERN = Pattern.compile("[^a-z0-9]+");
private final BrainPluginConfigService configService;
@@ -69,18 +72,34 @@ public ToolResult listTree(String spaceSlug) {
}
}
- public ToolResult searchPages(String spaceSlug, String query, Integer limit) {
+ public ToolResult searchPages(String spaceSlug, String query, String searchMode, Integer limit) {
String resolvedQuery = requireText(query, "query");
String space = resolveSpace(spaceSlug);
+ String resolvedMode = resolveSearchMode(searchMode, SEARCH_MODE_AUTO);
int resolvedLimit = resolveLimit(limit, DEFAULT_SEARCH_LIMIT);
try {
- JsonNode node = executeJson("GET", searchUrl(space, resolvedQuery, resolvedLimit), null);
+ JsonNode node = executeJson("POST", searchUrl(space),
+ searchPayload(resolvedQuery, resolvedMode, resolvedLimit));
return ToolResult.success(formatSearchResults(resolvedQuery, node), nodeToData(node));
} catch (IOException | IllegalStateException exception) {
return executionFailure(exception.getMessage());
}
}
+ public ToolResult getSearchStatus(String spaceSlug) {
+ String space = resolveSpace(spaceSlug);
+ try {
+ JsonNode node = executeJson("GET", searchStatusUrl(space), null);
+ String output = "Brain search status for " + space
+ + ": ready=" + node.path("ready").asBoolean(false)
+ + ", indexedDocuments=" + node.path("indexedDocuments").asInt(0)
+ + ", embeddingsReady=" + node.path("embeddingsReady").asBoolean(false);
+ return ToolResult.success(output, nodeToData(node));
+ } catch (IOException | IllegalStateException exception) {
+ return executionFailure(exception.getMessage());
+ }
+ }
+
public ToolResult intellisearch(
String spaceSlug,
String context,
@@ -117,7 +136,7 @@ public ToolResult readPage(String spaceSlug, String path) {
}
public ToolResult createPage(String spaceSlug, String parentPath, String title, String slug, String content,
- String kind) {
+ String kind, List tags, String summary) {
requireWriteAllowed();
String space = resolveSpace(spaceSlug);
try {
@@ -127,6 +146,12 @@ public ToolResult createPage(String spaceSlug, String parentPath, String title,
payload.put("slug", trimToNull(slug));
payload.put("content", valueOrEmpty(content));
payload.put("kind", trimToNull(kind) != null ? kind : "PAGE");
+ if (tags != null && !tags.isEmpty()) {
+ payload.put("tags", tags);
+ }
+ if (trimToNull(summary) != null) {
+ payload.put("summary", summary);
+ }
JsonNode node = executeJson("POST", spaceUrl(space, "/pages"), payload);
return ToolResult.success("Created Brain page " + node.path("path").asText(""), nodeToData(node));
} catch (IOException | IllegalStateException exception) {
@@ -135,7 +160,7 @@ public ToolResult createPage(String spaceSlug, String parentPath, String title,
}
public ToolResult updatePage(String spaceSlug, String path, String title, String slug, String content,
- String revision) {
+ String revision, List tags, String summary) {
requireWriteAllowed();
String pagePath = requireText(path, "path");
String space = resolveSpace(spaceSlug);
@@ -144,6 +169,12 @@ public ToolResult updatePage(String spaceSlug, String path, String title, String
payload.put("slug", trimToNull(slug));
payload.put("content", valueOrEmpty(content));
payload.put("revision", trimToNull(revision));
+ if (tags != null && !tags.isEmpty()) {
+ payload.put("tags", tags);
+ }
+ if (trimToNull(summary) != null) {
+ payload.put("summary", summary);
+ }
try {
JsonNode node = executeJson("PUT", pageUrl(space, pagePath), payload);
return ToolResult.success("Updated Brain page " + node.path("path").asText(pagePath), nodeToData(node));
@@ -152,6 +183,73 @@ public ToolResult updatePage(String spaceSlug, String path, String title, String
}
}
+ public ToolResult patchPage(String spaceSlug, String path, String operation, String heading, String content,
+ String expectedRevision) {
+ requireWriteAllowed();
+ String pagePath = requireText(path, "path");
+ String resolvedOperation = requireText(operation, "patch_operation");
+ String resolvedRevision = requireText(expectedRevision, "expected_revision");
+ String space = resolveSpace(spaceSlug);
+ Map payload = new LinkedHashMap<>();
+ payload.put("operation", resolvedOperation.toUpperCase(Locale.ROOT));
+ payload.put("expectedRevision", resolvedRevision);
+ payload.put("content", valueOrEmpty(content));
+ payload.put("heading", trimToNull(heading));
+ try {
+ JsonNode node = executeJson("PATCH", pageUrl(space, pagePath), payload);
+ return ToolResult.success("Patched Brain page " + pagePath + " (" + resolvedOperation + ")",
+ nodeToData(node));
+ } catch (IOException | IllegalStateException exception) {
+ return executionFailure(exception.getMessage());
+ }
+ }
+
+ public ToolResult getWikiGraph(String spaceSlug) {
+ String space = resolveSpace(spaceSlug);
+ try {
+ JsonNode node = executeJson("GET", spaceUrl(space, "/wiki/graph"), null);
+ int orphanCount = node.path("orphans").isArray() ? node.path("orphans").size() : 0;
+ int danglingCount = node.path("dangling").isArray() ? node.path("dangling").size() : 0;
+ return ToolResult.success(
+ "Brain graph for " + space + ": " + orphanCount + " orphan(s), " + danglingCount + " dangling",
+ nodeToData(node));
+ } catch (IOException | IllegalStateException exception) {
+ return executionFailure(exception.getMessage());
+ }
+ }
+
+ public ToolResult listTopAccessed(String spaceSlug, Integer limit) {
+ String space = resolveSpace(spaceSlug);
+ int resolvedLimit = resolveLimit(limit, DEFAULT_SEARCH_LIMIT);
+ try {
+ JsonNode node = executeJson("GET",
+ spaceUrl(space, "/wiki/access/top").newBuilder()
+ .addQueryParameter("limit", Integer.toString(resolvedLimit)).build(),
+ null);
+ int count = node.path("items").isArray() ? node.path("items").size() : 0;
+ return ToolResult.success("Listed " + count + " top Brain page(s) by access count", nodeToData(node));
+ } catch (IOException | IllegalStateException exception) {
+ return executionFailure(exception.getMessage());
+ }
+ }
+
+ public ToolResult applyTransaction(String spaceSlug, List