memorylake-openclaw 0.0.5 → 0.0.6

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/README.md CHANGED
@@ -37,7 +37,7 @@ Get an API key from [app.memorylake.ai](https://app.memorylake.ai), then add to
37
37
 
38
38
  ## Agent tools
39
39
 
40
- The agent gets six tools it can call during conversations:
40
+ The agent gets seven tools it can call during conversations:
41
41
 
42
42
  | Tool | Description |
43
43
  |------|-------------|
@@ -47,6 +47,7 @@ The agent gets six tools it can call during conversations:
47
47
  | `memory_get` | Retrieve a memory by ID |
48
48
  | `memory_forget` | Delete a memory by ID |
49
49
  | `document_search` | Search project documents for relevant paragraphs, tables, and figures |
50
+ | `advanced_web_search` | Optional tool for web search with plugin-level domain and locale constraints |
50
51
 
51
52
  ## CLI
52
53
 
@@ -70,6 +71,12 @@ openclaw memorylake stats
70
71
  | `topK` | `number` | `5` | Max memories per recall |
71
72
  | `searchThreshold` | `number` | `0.3` | Min similarity (0–1) |
72
73
  | `rerank` | `boolean` | `true` | Rerank search results |
74
+ | `webSearchIncludeDomains` | `string[]` | — | Optional allowlist for `advanced_web_search` results |
75
+ | `webSearchExcludeDomains` | `string[]` | — | Optional denylist for `advanced_web_search` results |
76
+ | `webSearchCountry` | `string` | — | Optional ISO country code for localizing `advanced_web_search` |
77
+ | `webSearchTimezone` | `string` | — | Optional IANA timezone for localizing `advanced_web_search` |
78
+
79
+ `advanced_web_search` is registered as an optional OpenClaw tool, so agents must explicitly allow it before they can call it.
73
80
 
74
81
  ## License
75
82
 
package/docs/openclaw.mdx CHANGED
@@ -13,7 +13,7 @@ Add long-term memory to [OpenClaw](https://github.com/openclaw/openclaw) agents
13
13
  The plugin provides:
14
14
  1. **Auto-Recall** — Before the agent responds, memories and relevant document excerpts matching the current message are injected into context
15
15
  2. **Auto-Capture** — After the agent responds, the exchange is sent to MemoryLake which decides what's worth keeping
16
- 3. **Agent Tools** — Six tools for memory and document operations during conversations
16
+ 3. **Agent Tools** — Seven tools for memory, document, and optional web search operations during conversations
17
17
 
18
18
  Both auto-recall and auto-capture run silently with no manual configuration required.
19
19
 
@@ -42,7 +42,7 @@ Add to your `openclaw.json`:
42
42
 
43
43
  ## Agent Tools
44
44
 
45
- The agent gets six tools it can call during conversations:
45
+ The agent gets seven tools it can call during conversations:
46
46
 
47
47
  | Tool | Description |
48
48
  |------|-------------|
@@ -52,6 +52,7 @@ The agent gets six tools it can call during conversations:
52
52
  | `memory_get` | Retrieve a memory by ID |
53
53
  | `memory_forget` | Delete a memory by ID |
54
54
  | `document_search` | Search project documents for relevant paragraphs, tables, and figures |
55
+ | `advanced_web_search` | Optional web search tool backed by the unified search API with plugin-level domain and locale constraints |
55
56
 
56
57
  ## CLI Commands
57
58
 
@@ -75,13 +76,19 @@ openclaw memorylake stats
75
76
  | `topK` | `number` | `5` | Max memories per recall |
76
77
  | `searchThreshold` | `number` | `0.3` | Min similarity (0–1) |
77
78
  | `rerank` | `boolean` | `true` | Rerank search results for better relevance |
79
+ | `webSearchIncludeDomains` | `string[]` | — | Optional allowlist for `advanced_web_search` results |
80
+ | `webSearchExcludeDomains` | `string[]` | — | Optional denylist for `advanced_web_search` results |
81
+ | `webSearchCountry` | `string` | — | Optional ISO country code for localizing `advanced_web_search` |
82
+ | `webSearchTimezone` | `string` | — | Optional IANA timezone for localizing `advanced_web_search` |
83
+
84
+ <Note>`advanced_web_search` is registered as an optional OpenClaw tool, so it must be explicitly allowed before an agent can call it.</Note>
78
85
 
79
86
  ## Key Features
80
87
 
81
88
  1. **Zero Configuration** — Auto-recall and auto-capture work out of the box with no prompting required
82
89
  2. **Async Processing** — Memory extraction runs asynchronously via MemoryLake's API
83
90
  3. **Session Tracking** — Conversations are tagged with `chat_session_id` for traceability
84
- 4. **Rich Tool Suite** — Six agent tools for memory and document operations when needed
91
+ 4. **Rich Tool Suite** — Seven agent tools for memory, document, and optional web search operations when needed
85
92
 
86
93
  ## Conclusion
87
94
 
package/index.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Long-term memory via MemoryLake platform.
5
5
  *
6
6
  * Features:
7
- * - 6 tools: memory_search, memory_list, memory_store, memory_get, memory_forget, document_search
7
+ * - 7 tools: memory_search, memory_list, memory_store, memory_get, memory_forget, document_search, advanced_web_search
8
8
  * - Auto-recall: injects relevant memories and document excerpts before each agent turn
9
9
  * - Auto-capture: stores key facts scoped to the current session after each agent turn
10
10
  * - CLI: openclaw memorylake search, openclaw memorylake stats
@@ -29,6 +29,10 @@ type MemoryLakeConfig = {
29
29
  searchThreshold: number;
30
30
  topK: number;
31
31
  rerank: boolean;
32
+ webSearchIncludeDomains?: string[];
33
+ webSearchExcludeDomains?: string[];
34
+ webSearchCountry?: string;
35
+ webSearchTimezone?: string;
32
36
  };
33
37
 
34
38
  // V2 API option types
@@ -99,6 +103,68 @@ interface DocumentSearchResponse {
99
103
  results: DocumentSearchResult[];
100
104
  }
101
105
 
106
+ /**
107
+ * Allowed values for web search domain (aligned with zootopia unified_search Domain).
108
+ * Declared as enum in schema; at runtime accept string and fall back to "auto" if invalid.
109
+ */
110
+ const WebSearchDomainValues = [
111
+ "web",
112
+ "academic",
113
+ "news",
114
+ "people",
115
+ "company",
116
+ "financial",
117
+ "markets",
118
+ "code",
119
+ "legal",
120
+ "government",
121
+ "poi",
122
+ "auto",
123
+ ] as const;
124
+ type WebSearchDomain = (typeof WebSearchDomainValues)[number];
125
+
126
+ const WEB_SEARCH_DOMAIN_SET = new Set<string>(WebSearchDomainValues);
127
+
128
+ /** Normalize domain: accept string at runtime; if not a valid enum value, return "auto". */
129
+ function normalizeWebSearchDomain(value: unknown): WebSearchDomain {
130
+ if (value == null) return "auto";
131
+ const s = typeof value === "string" ? value.toLowerCase().trim() : "";
132
+ return (WEB_SEARCH_DOMAIN_SET.has(s) ? s : "auto") as WebSearchDomain;
133
+ }
134
+
135
+ interface WebSearchUserLocation {
136
+ country?: string;
137
+ timezone?: string;
138
+ }
139
+
140
+ interface WebSearchOptions {
141
+ /** Declared as enum in schema; at runtime accept string, normalized with fallback to "auto". */
142
+ domain?: WebSearchDomain | string;
143
+ max_results?: number;
144
+ start_date?: string;
145
+ end_date?: string;
146
+ include_domains?: string[];
147
+ exclude_domains?: string[];
148
+ user_location?: WebSearchUserLocation;
149
+ }
150
+
151
+ interface WebSearchResult {
152
+ url?: string;
153
+ title?: string;
154
+ summary?: string;
155
+ content?: string;
156
+ source?: string;
157
+ published_date?: string;
158
+ author?: string;
159
+ score?: number;
160
+ highlights?: string[];
161
+ }
162
+
163
+ interface WebSearchResponse {
164
+ results: WebSearchResult[];
165
+ total_results: number;
166
+ }
167
+
102
168
  // ============================================================================
103
169
  // Unified Provider Interface
104
170
  // ============================================================================
@@ -113,6 +179,7 @@ interface MemoryLakeProvider {
113
179
  getAll(options: ListOptions): Promise<MemoryItem[]>;
114
180
  delete(memoryId: string): Promise<void>;
115
181
  searchDocuments(query: string, topN: number): Promise<DocumentSearchResponse>;
182
+ searchWeb(query: string, options: WebSearchOptions): Promise<WebSearchResponse>;
116
183
  }
117
184
 
118
185
  // ============================================================================
@@ -130,10 +197,12 @@ class PlatformProvider implements MemoryLakeProvider {
130
197
  private readonly http: ReturnType<typeof got.extend>;
131
198
  private readonly basePath: string;
132
199
  private readonly docSearchPath: string;
200
+ private readonly webSearchPath: string;
133
201
 
134
202
  constructor(host: string, apiKey: string, projectId: string) {
135
203
  this.basePath = `openapi/memorylake/api/v2/projects/${projectId}/memories`;
136
204
  this.docSearchPath = `openapi/memorylake/api/v1/projects/${projectId}/documents/search`;
205
+ this.webSearchPath = "openapi/memorylake/api/v1/search";
137
206
  this.http = got.extend({
138
207
  prefixUrl: host,
139
208
  headers: {
@@ -220,6 +289,25 @@ class PlatformProvider implements MemoryLakeProvider {
220
289
  results: Array.isArray(data?.results) ? data.results : [],
221
290
  };
222
291
  }
292
+
293
+ async searchWeb(query: string, options: WebSearchOptions): Promise<WebSearchResponse> {
294
+ const domain = options.domain != null ? normalizeWebSearchDomain(options.domain) : "web";
295
+ const body: Record<string, unknown> = {
296
+ query,
297
+ domain,
298
+ };
299
+ if (options.max_results != null) body.max_results = options.max_results;
300
+ if (options.start_date) body.start_date = options.start_date;
301
+ if (options.end_date) body.end_date = options.end_date;
302
+ if (options.include_domains?.length) body.include_domains = options.include_domains;
303
+ if (options.exclude_domains?.length) body.exclude_domains = options.exclude_domains;
304
+ if (options.user_location) body.user_location = options.user_location;
305
+
306
+ const resp = await this.http
307
+ .post(this.webSearchPath, { json: body })
308
+ .json<WebSearchResponse>();
309
+ return normalizeWebSearchResponse(resp);
310
+ }
223
311
  }
224
312
 
225
313
  // ============================================================================
@@ -254,6 +342,13 @@ function normalizeAddResult(raw: any): AddResult {
254
342
  };
255
343
  }
256
344
 
345
+ function normalizeWebSearchResponse(raw: any): WebSearchResponse {
346
+ return {
347
+ results: Array.isArray(raw?.results) ? raw.results : [],
348
+ total_results: typeof raw?.total_results === "number" ? raw.total_results : 0,
349
+ };
350
+ }
351
+
257
352
  // ============================================================================
258
353
  // Document Context Builder
259
354
  // ============================================================================
@@ -302,6 +397,19 @@ function buildDocumentContext(
302
397
  return parts.join("\n\n");
303
398
  }
304
399
 
400
+ function buildWebSearchContext(results: WebSearchResult[]): string {
401
+ return results
402
+ .map((result, index) => {
403
+ const parts = [`${index + 1}. ${result.title ?? result.url ?? "Untitled result"}`];
404
+ if (result.url) parts.push(`URL: ${result.url}`);
405
+ if (result.summary) parts.push(`Summary: ${result.summary}`);
406
+ if (result.source) parts.push(`Source: ${result.source}`);
407
+ if (result.published_date) parts.push(`Published: ${result.published_date}`);
408
+ return parts.join("\n");
409
+ })
410
+ .join("\n\n");
411
+ }
412
+
305
413
  // ============================================================================
306
414
  // Config Parser
307
415
  // ============================================================================
@@ -320,6 +428,10 @@ const ALLOWED_KEYS = [
320
428
  "searchThreshold",
321
429
  "topK",
322
430
  "rerank",
431
+ "webSearchIncludeDomains",
432
+ "webSearchExcludeDomains",
433
+ "webSearchCountry",
434
+ "webSearchTimezone",
323
435
  ];
324
436
 
325
437
  function assertAllowedKeys(
@@ -332,6 +444,28 @@ function assertAllowedKeys(
332
444
  throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`);
333
445
  }
334
446
 
447
+ function parseOptionalStringArray(
448
+ value: unknown,
449
+ label: string,
450
+ ): string[] | undefined {
451
+ if (value == null) return undefined;
452
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
453
+ throw new Error(`${label} must be an array of strings`);
454
+ }
455
+ return value;
456
+ }
457
+
458
+ function parseOptionalString(
459
+ value: unknown,
460
+ label: string,
461
+ ): string | undefined {
462
+ if (value == null) return undefined;
463
+ if (typeof value !== "string") {
464
+ throw new Error(`${label} must be a string`);
465
+ }
466
+ return value;
467
+ }
468
+
335
469
  const memoryLakeConfigSchema = {
336
470
  parse(value: unknown): MemoryLakeConfig {
337
471
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -361,6 +495,22 @@ const memoryLakeConfigSchema = {
361
495
  typeof cfg.searchThreshold === "number" ? cfg.searchThreshold : 0.3,
362
496
  topK: typeof cfg.topK === "number" ? cfg.topK : 5,
363
497
  rerank: cfg.rerank !== false,
498
+ webSearchIncludeDomains: parseOptionalStringArray(
499
+ cfg.webSearchIncludeDomains,
500
+ "webSearchIncludeDomains",
501
+ ),
502
+ webSearchExcludeDomains: parseOptionalStringArray(
503
+ cfg.webSearchExcludeDomains,
504
+ "webSearchExcludeDomains",
505
+ ),
506
+ webSearchCountry: parseOptionalString(
507
+ cfg.webSearchCountry,
508
+ "webSearchCountry",
509
+ ),
510
+ webSearchTimezone: parseOptionalString(
511
+ cfg.webSearchTimezone,
512
+ "webSearchTimezone",
513
+ ),
364
514
  };
365
515
  },
366
516
  };
@@ -783,6 +933,134 @@ const memoryPlugin = {
783
933
  { name: "document_search" },
784
934
  );
785
935
 
936
+ api.registerTool(
937
+ {
938
+ name: "advanced_web_search",
939
+ label: "Advanced Web Search",
940
+ description:
941
+ "Search the web using the unified search API with plugin-level domain and location constraints. Use this for recent information, public web pages, or web research that should respect configured allowed domains, blocked domains, and user locale.",
942
+ parameters: Type.Object({
943
+ query: Type.String({
944
+ description:
945
+ "The web search query to send to the unified search endpoint.",
946
+ }),
947
+ domain: Type.Optional(
948
+ Type.Union(
949
+ [
950
+ Type.Literal("web"),
951
+ Type.Literal("academic"),
952
+ Type.Literal("news"),
953
+ Type.Literal("people"),
954
+ Type.Literal("company"),
955
+ Type.Literal("financial"),
956
+ Type.Literal("markets"),
957
+ Type.Literal("code"),
958
+ Type.Literal("legal"),
959
+ Type.Literal("government"),
960
+ Type.Literal("poi"),
961
+ Type.Literal("auto"),
962
+ ],
963
+ {
964
+ description:
965
+ "Search domain. Default: web. Invalid or unknown values are treated as auto.",
966
+ },
967
+ ),
968
+ ),
969
+ maxResults: Type.Optional(
970
+ Type.Number({
971
+ description: `Maximum number of web results to return (default: ${cfg.topK}).`,
972
+ minimum: 1,
973
+ }),
974
+ ),
975
+ startDate: Type.Optional(
976
+ Type.String({
977
+ description:
978
+ "Only include results published on or after this date (YYYY-MM-DD).",
979
+ }),
980
+ ),
981
+ endDate: Type.Optional(
982
+ Type.String({
983
+ description:
984
+ "Only include results published on or before this date (YYYY-MM-DD).",
985
+ }),
986
+ ),
987
+ }),
988
+ async execute(_toolCallId, params) {
989
+ const {
990
+ query,
991
+ domain: rawDomain,
992
+ maxResults,
993
+ startDate,
994
+ endDate,
995
+ } = params as {
996
+ query: string;
997
+ domain?: string;
998
+ maxResults?: number;
999
+ startDate?: string;
1000
+ endDate?: string;
1001
+ };
1002
+ const domain: WebSearchDomain =
1003
+ rawDomain === undefined || rawDomain === null
1004
+ ? "web"
1005
+ : normalizeWebSearchDomain(rawDomain);
1006
+
1007
+ try {
1008
+ const response = await provider.searchWeb(query, {
1009
+ domain,
1010
+ max_results: maxResults ?? cfg.topK,
1011
+ start_date: startDate,
1012
+ end_date: endDate,
1013
+ include_domains: cfg.webSearchIncludeDomains,
1014
+ exclude_domains: cfg.webSearchExcludeDomains,
1015
+ user_location:
1016
+ cfg.webSearchCountry || cfg.webSearchTimezone
1017
+ ? {
1018
+ country: cfg.webSearchCountry,
1019
+ timezone: cfg.webSearchTimezone,
1020
+ }
1021
+ : undefined,
1022
+ });
1023
+
1024
+ if (!response.results || response.results.length === 0) {
1025
+ return {
1026
+ content: [
1027
+ { type: "text", text: "No relevant web results found." },
1028
+ ],
1029
+ details: { count: 0, total_results: response.total_results },
1030
+ };
1031
+ }
1032
+
1033
+ const context = buildWebSearchContext(response.results);
1034
+
1035
+ return {
1036
+ content: [
1037
+ {
1038
+ type: "text",
1039
+ text: `Found ${response.results.length} web results:\n\n${context}`,
1040
+ },
1041
+ ],
1042
+ details: {
1043
+ count: response.results.length,
1044
+ total_results: response.total_results,
1045
+ results: response.results,
1046
+ },
1047
+ };
1048
+ } catch (err) {
1049
+ return {
1050
+ content: [
1051
+ {
1052
+ type: "text",
1053
+ text: `Web search failed: ${String(err)}`,
1054
+ },
1055
+ ],
1056
+ details: { error: String(err) },
1057
+ };
1058
+ }
1059
+ },
1060
+ },
1061
+ { optional: true },
1062
+ );
1063
+
786
1064
  // ========================================================================
787
1065
  // CLI Commands
788
1066
  // ========================================================================
@@ -35,6 +35,26 @@
35
35
  "rerank": {
36
36
  "label": "Rerank",
37
37
  "help": "Rerank search results"
38
+ },
39
+ "webSearchIncludeDomains": {
40
+ "label": "Web Search Allowed Domains",
41
+ "placeholder": "[\"example.com\", \"docs.example.com\"]",
42
+ "help": "Optional allowlist for advanced_web_search results."
43
+ },
44
+ "webSearchExcludeDomains": {
45
+ "label": "Web Search Blocked Domains",
46
+ "placeholder": "[\"example.com\"]",
47
+ "help": "Optional denylist for advanced_web_search results."
48
+ },
49
+ "webSearchCountry": {
50
+ "label": "Web Search Country",
51
+ "placeholder": "US",
52
+ "help": "Optional ISO country code used to localize advanced_web_search results."
53
+ },
54
+ "webSearchTimezone": {
55
+ "label": "Web Search Timezone",
56
+ "placeholder": "America/Los_Angeles",
57
+ "help": "Optional IANA timezone used to localize advanced_web_search results."
38
58
  }
39
59
  },
40
60
  "configSchema": {
@@ -64,6 +84,24 @@
64
84
  },
65
85
  "rerank": {
66
86
  "type": "boolean"
87
+ },
88
+ "webSearchIncludeDomains": {
89
+ "type": "array",
90
+ "items": {
91
+ "type": "string"
92
+ }
93
+ },
94
+ "webSearchExcludeDomains": {
95
+ "type": "array",
96
+ "items": {
97
+ "type": "string"
98
+ }
99
+ },
100
+ "webSearchCountry": {
101
+ "type": "string"
102
+ },
103
+ "webSearchTimezone": {
104
+ "type": "string"
67
105
  }
68
106
  },
69
107
  "required": []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorylake-openclaw",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "type": "module",
5
5
  "description": "MemoryLake memory backend for OpenClaw",
6
6
  "license": "MIT",