@susu-eng/gralkor 26.0.19 → 27.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tools.js CHANGED
@@ -11,7 +11,10 @@ export function formatFacts(facts) {
11
11
  if (facts.length === 0)
12
12
  return "No graph facts found.";
13
13
  const lines = facts.map(formatFact).join("\n");
14
- return `Facts (knowledge graph):\n${lines}`;
14
+ return `Memories:\n${lines}`;
15
+ }
16
+ export function formatNode(n) {
17
+ return `- ${n.name}: ${n.summary ?? "(no summary)"}`;
15
18
  }
16
19
  export function createMemoryStoreTool(client, config, opts = {}) {
17
20
  const { getGroupId, serverReady } = opts;
@@ -53,6 +56,43 @@ export function createMemoryStoreTool(client, config, opts = {}) {
53
56
  },
54
57
  };
55
58
  }
59
+ export function createMemorySearchTool(client, config, opts = {}) {
60
+ const { getGroupId, serverReady } = opts;
61
+ return {
62
+ name: "memory_search",
63
+ description: "Search memory for relevant context. Use specific, focused queries.",
64
+ parameters: {
65
+ type: "object",
66
+ properties: {
67
+ query: { type: "string" },
68
+ limit: { type: "number" },
69
+ },
70
+ required: ["query"],
71
+ },
72
+ async execute(_toolCallId, args) {
73
+ if (serverReady && !serverReady.isReady()) {
74
+ throw new Error("[gralkor] memory_search failed: server is not ready");
75
+ }
76
+ const groupId = getGroupId?.() ?? "default";
77
+ const limit = args.limit ?? 10;
78
+ const results = await client.search(args.query, [groupId], limit, "slow");
79
+ const factCount = results.facts.length;
80
+ const nodeCount = results.nodes.length;
81
+ console.log(`[gralkor] memory_search result — graph: ${factCount} facts ${nodeCount} nodes — groupId:${groupId}`);
82
+ if (factCount === 0 && nodeCount === 0)
83
+ return "No memories found.";
84
+ const nodeSection = nodeCount > 0
85
+ ? "\n\nEntities:\n" + results.nodes.map(formatNode).join("\n")
86
+ : "";
87
+ const output = formatFacts(results.facts) + nodeSection + "\n\n" + INTERPRETATION_INSTRUCTION;
88
+ if (config.test) {
89
+ console.log(`[gralkor] [test] memory_search query: ${args.query}`);
90
+ console.log(`[gralkor] [test] memory_search result:\n${output}`);
91
+ }
92
+ return output;
93
+ },
94
+ };
95
+ }
56
96
  export function createBuildIndicesTool(client, opts = {}) {
57
97
  const { serverReady } = opts;
58
98
  return {
package/dist/tools.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
@@ -98,7 +98,6 @@
98
98
  },
99
99
  "tools": [
100
100
  "memory_search",
101
- "memory_get",
102
101
  "memory_add"
103
102
  ],
104
103
  "uiHints": {
@@ -139,5 +138,5 @@
139
138
  "label": "Groq API key"
140
139
  }
141
140
  },
142
- "version": "26.0.19"
141
+ "version": "27.0.1"
143
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@susu-eng/gralkor",
3
- "version": "26.0.19",
3
+ "version": "27.0.1",
4
4
  "description": "OpenClaw memory plugin powered by Graphiti knowledge graphs and FalkorDB",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -47,10 +47,11 @@
47
47
  "node": ">=18"
48
48
  },
49
49
  "peerDependencies": {
50
- "openclaw": ">= 2026.1.26"
50
+ "openclaw": ">= 2026.4.2"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "^25.0.0",
54
+ "memfs": "^4.57.1",
54
55
  "typescript": "^5.7.0",
55
56
  "vitest": "^4.1.2"
56
57
  },
@@ -61,6 +62,10 @@
61
62
  "test": "pnpm run typecheck && pnpm run test:plugin && pnpm run test:integration && pnpm run test:server",
62
63
  "test:plugin": "vitest run",
63
64
  "test:integration": "vitest run --config test/integration/vitest.config.ts",
65
+ "test:functional": "bash test/harness/functional-env.sh test",
66
+ "test:functional:up": "bash test/harness/functional-env.sh up",
67
+ "test:functional:run": "bash test/harness/functional-env.sh run",
68
+ "test:functional:down": "bash test/harness/functional-env.sh down",
64
69
  "test:server": "cd server && uv run pytest tests/",
65
70
  "test:server:changed": "cd server && files=$(git diff --name-only --diff-filter=d HEAD -- 'tests/*.py') && [ -n \"$files\" ] && uv run pytest $files || echo 'No changed server test files'",
66
71
  "test:mutate": "stryker run",
@@ -70,10 +75,6 @@
70
75
  "docker:up": "pnpm run docker:build && docker compose up -d",
71
76
  "docker:down": "docker compose down",
72
77
  "docker:logs": "docker compose logs graphiti",
73
- "harness": "bash test/harness/build.sh",
74
- "harness:npm": "bash test/harness/build.sh --npm",
75
- "harness:run": "docker run --rm -it --platform linux/amd64 gralkor-harness:latest",
76
- "harness:shell": "docker run --rm -it --platform linux/amd64 gralkor-harness:latest bash",
77
78
  "publish:npm": "bash scripts/publish.sh"
78
79
  }
79
80
  }
package/server/main.py CHANGED
@@ -7,6 +7,7 @@ import logging
7
7
  import os
8
8
  import time
9
9
  from contextlib import asynccontextmanager
10
+ from copy import deepcopy
10
11
  from dataclasses import dataclass, field
11
12
  from datetime import datetime, timezone
12
13
  from typing import Any, Literal
@@ -23,6 +24,7 @@ from graphiti_core.driver.falkordb_driver import FalkorDriver
23
24
  from graphiti_core.edges import EntityEdge
24
25
  from graphiti_core.nodes import EpisodicNode, EpisodeType
25
26
  from graphiti_core.llm_client import LLMConfig
27
+ from graphiti_core.search.search_config_recipes import COMBINED_HYBRID_SEARCH_CROSS_ENCODER
26
28
 
27
29
 
28
30
  # ── Config ────────────────────────────────────────────────────
@@ -386,6 +388,7 @@ class SearchRequest(BaseModel):
386
388
  query: str
387
389
  group_ids: list[str]
388
390
  num_results: int = 10
391
+ mode: Literal["fast", "slow"] = "fast"
389
392
 
390
393
 
391
394
  class GroupIdRequest(BaseModel):
@@ -399,6 +402,15 @@ def _ts(dt: datetime | None) -> str | None:
399
402
  return dt.isoformat() if dt else None
400
403
 
401
404
 
405
+ def _serialize_node(node) -> dict[str, Any]:
406
+ return {
407
+ "uuid": node.uuid,
408
+ "name": node.name,
409
+ "summary": node.summary,
410
+ "group_id": node.group_id,
411
+ }
412
+
413
+
402
414
  def _serialize_fact(edge: EntityEdge) -> dict[str, Any]:
403
415
  return {
404
416
  "uuid": edge.uuid,
@@ -698,8 +710,8 @@ def _prioritize_facts(
698
710
 
699
711
  @app.post("/search")
700
712
  async def search(req: SearchRequest):
701
- logger.info("[gralkor] search — query:%d chars group_ids:%s num_results:%d",
702
- len(req.query), req.group_ids, req.num_results)
713
+ logger.info("[gralkor] search — mode:%s query:%d chars group_ids:%s num_results:%d",
714
+ req.mode, len(req.query), req.group_ids, req.num_results)
703
715
  # graphiti.add_episode() clones the driver to target the correct FalkorDB
704
716
  # named graph (database=group_id), but graphiti.search() does not — it just
705
717
  # uses whatever graph the driver currently points at. Before the first
@@ -710,23 +722,40 @@ async def search(req: SearchRequest):
710
722
  # Over-fetch to compensate for expired facts that will be deprioritized.
711
723
  fetch_limit = req.num_results * 2
712
724
  try:
713
- edges = await graphiti.search(
714
- query=_sanitize_query(req.query),
715
- group_ids=req.group_ids,
716
- num_results=fetch_limit,
717
- )
725
+ if req.mode == "slow":
726
+ # Cross-encoder + BFS: higher quality, also returns entity node summaries.
727
+ # deepcopy required — COMBINED_HYBRID_SEARCH_CROSS_ENCODER is a module-level
728
+ # constant; mutating .limit directly would corrupt it across requests.
729
+ config = deepcopy(COMBINED_HYBRID_SEARCH_CROSS_ENCODER)
730
+ config.limit = fetch_limit
731
+ search_result = await graphiti.search_(
732
+ query=_sanitize_query(req.query),
733
+ group_ids=req.group_ids,
734
+ config=config,
735
+ )
736
+ edges = search_result.edges
737
+ nodes = search_result.nodes
738
+ else:
739
+ edges = await graphiti.search(
740
+ query=_sanitize_query(req.query),
741
+ group_ids=req.group_ids,
742
+ num_results=fetch_limit,
743
+ )
744
+ nodes = []
718
745
  except Exception as e:
719
746
  duration_ms = (time.monotonic() - t0) * 1000
720
- logger.error("[gralkor] search failed — %.0fms: %s", duration_ms, e)
747
+ logger.error("[gralkor] search failed — mode:%s %.0fms: %s", req.mode, duration_ms, e)
721
748
  raise
722
749
  duration_ms = (time.monotonic() - t0) * 1000
723
750
  prioritized = _prioritize_facts(edges, req.num_results)
724
751
  valid_count = sum(1 for e in prioritized if e.invalid_at is None)
725
752
  result = [_serialize_fact(e) for e in prioritized]
726
- logger.info("[gralkor] search result — %d facts (%d valid, %d non-valid) from %d fetched %.0fms",
727
- len(prioritized), valid_count, len(prioritized) - valid_count, len(edges), duration_ms)
753
+ serialized_nodes = [_serialize_node(n) for n in nodes]
754
+ logger.info("[gralkor] search result mode:%s %d facts (%d valid, %d non-valid) %d nodes from %d fetched %.0fms",
755
+ req.mode, len(prioritized), valid_count, len(prioritized) - valid_count,
756
+ len(serialized_nodes), len(edges), duration_ms)
728
757
  logger.debug("[gralkor] search facts: %s", result)
729
- return {"facts": result}
758
+ return {"facts": result, "nodes": serialized_nodes}
730
759
 
731
760
 
732
761