memorylake-openclaw 0.0.5 → 0.0.7
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 +8 -1
- package/docs/openclaw.mdx +10 -3
- package/index.ts +383 -34
- package/openclaw.plugin.json +38 -0
- package/package.json +1 -1
- package/skills/agent-memorylake-config/SKILL.md +43 -0
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
|
|
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** —
|
|
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
|
|
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** —
|
|
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,12 +4,14 @@
|
|
|
4
4
|
* Long-term memory via MemoryLake platform.
|
|
5
5
|
*
|
|
6
6
|
* Features:
|
|
7
|
-
* -
|
|
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
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
13
15
|
import got from "got";
|
|
14
16
|
import { Type } from "@sinclair/typebox";
|
|
15
17
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
@@ -29,6 +31,10 @@ type MemoryLakeConfig = {
|
|
|
29
31
|
searchThreshold: number;
|
|
30
32
|
topK: number;
|
|
31
33
|
rerank: boolean;
|
|
34
|
+
webSearchIncludeDomains?: string[];
|
|
35
|
+
webSearchExcludeDomains?: string[];
|
|
36
|
+
webSearchCountry?: string;
|
|
37
|
+
webSearchTimezone?: string;
|
|
32
38
|
};
|
|
33
39
|
|
|
34
40
|
// V2 API option types
|
|
@@ -99,6 +105,68 @@ interface DocumentSearchResponse {
|
|
|
99
105
|
results: DocumentSearchResult[];
|
|
100
106
|
}
|
|
101
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Allowed values for web search domain (aligned with zootopia unified_search Domain).
|
|
110
|
+
* Declared as enum in schema; at runtime accept string and fall back to "auto" if invalid.
|
|
111
|
+
*/
|
|
112
|
+
const WebSearchDomainValues = [
|
|
113
|
+
"web",
|
|
114
|
+
"academic",
|
|
115
|
+
"news",
|
|
116
|
+
"people",
|
|
117
|
+
"company",
|
|
118
|
+
"financial",
|
|
119
|
+
"markets",
|
|
120
|
+
"code",
|
|
121
|
+
"legal",
|
|
122
|
+
"government",
|
|
123
|
+
"poi",
|
|
124
|
+
"auto",
|
|
125
|
+
] as const;
|
|
126
|
+
type WebSearchDomain = (typeof WebSearchDomainValues)[number];
|
|
127
|
+
|
|
128
|
+
const WEB_SEARCH_DOMAIN_SET = new Set<string>(WebSearchDomainValues);
|
|
129
|
+
|
|
130
|
+
/** Normalize domain: accept string at runtime; if not a valid enum value, return "auto". */
|
|
131
|
+
function normalizeWebSearchDomain(value: unknown): WebSearchDomain {
|
|
132
|
+
if (value == null) return "auto";
|
|
133
|
+
const s = typeof value === "string" ? value.toLowerCase().trim() : "";
|
|
134
|
+
return (WEB_SEARCH_DOMAIN_SET.has(s) ? s : "auto") as WebSearchDomain;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface WebSearchUserLocation {
|
|
138
|
+
country?: string;
|
|
139
|
+
timezone?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface WebSearchOptions {
|
|
143
|
+
/** Declared as enum in schema; at runtime accept string, normalized with fallback to "auto". */
|
|
144
|
+
domain?: WebSearchDomain | string;
|
|
145
|
+
max_results?: number;
|
|
146
|
+
start_date?: string;
|
|
147
|
+
end_date?: string;
|
|
148
|
+
include_domains?: string[];
|
|
149
|
+
exclude_domains?: string[];
|
|
150
|
+
user_location?: WebSearchUserLocation;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface WebSearchResult {
|
|
154
|
+
url?: string;
|
|
155
|
+
title?: string;
|
|
156
|
+
summary?: string;
|
|
157
|
+
content?: string;
|
|
158
|
+
source?: string;
|
|
159
|
+
published_date?: string;
|
|
160
|
+
author?: string;
|
|
161
|
+
score?: number;
|
|
162
|
+
highlights?: string[];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
interface WebSearchResponse {
|
|
166
|
+
results: WebSearchResult[];
|
|
167
|
+
total_results: number;
|
|
168
|
+
}
|
|
169
|
+
|
|
102
170
|
// ============================================================================
|
|
103
171
|
// Unified Provider Interface
|
|
104
172
|
// ============================================================================
|
|
@@ -113,6 +181,7 @@ interface MemoryLakeProvider {
|
|
|
113
181
|
getAll(options: ListOptions): Promise<MemoryItem[]>;
|
|
114
182
|
delete(memoryId: string): Promise<void>;
|
|
115
183
|
searchDocuments(query: string, topN: number): Promise<DocumentSearchResponse>;
|
|
184
|
+
searchWeb(query: string, options: WebSearchOptions): Promise<WebSearchResponse>;
|
|
116
185
|
}
|
|
117
186
|
|
|
118
187
|
// ============================================================================
|
|
@@ -130,10 +199,12 @@ class PlatformProvider implements MemoryLakeProvider {
|
|
|
130
199
|
private readonly http: ReturnType<typeof got.extend>;
|
|
131
200
|
private readonly basePath: string;
|
|
132
201
|
private readonly docSearchPath: string;
|
|
202
|
+
private readonly webSearchPath: string;
|
|
133
203
|
|
|
134
204
|
constructor(host: string, apiKey: string, projectId: string) {
|
|
135
205
|
this.basePath = `openapi/memorylake/api/v2/projects/${projectId}/memories`;
|
|
136
206
|
this.docSearchPath = `openapi/memorylake/api/v1/projects/${projectId}/documents/search`;
|
|
207
|
+
this.webSearchPath = "openapi/memorylake/api/v1/search";
|
|
137
208
|
this.http = got.extend({
|
|
138
209
|
prefixUrl: host,
|
|
139
210
|
headers: {
|
|
@@ -220,6 +291,25 @@ class PlatformProvider implements MemoryLakeProvider {
|
|
|
220
291
|
results: Array.isArray(data?.results) ? data.results : [],
|
|
221
292
|
};
|
|
222
293
|
}
|
|
294
|
+
|
|
295
|
+
async searchWeb(query: string, options: WebSearchOptions): Promise<WebSearchResponse> {
|
|
296
|
+
const domain = options.domain != null ? normalizeWebSearchDomain(options.domain) : "web";
|
|
297
|
+
const body: Record<string, unknown> = {
|
|
298
|
+
query,
|
|
299
|
+
domain,
|
|
300
|
+
};
|
|
301
|
+
if (options.max_results != null) body.max_results = options.max_results;
|
|
302
|
+
if (options.start_date) body.start_date = options.start_date;
|
|
303
|
+
if (options.end_date) body.end_date = options.end_date;
|
|
304
|
+
if (options.include_domains?.length) body.include_domains = options.include_domains;
|
|
305
|
+
if (options.exclude_domains?.length) body.exclude_domains = options.exclude_domains;
|
|
306
|
+
if (options.user_location) body.user_location = options.user_location;
|
|
307
|
+
|
|
308
|
+
const resp = await this.http
|
|
309
|
+
.post(this.webSearchPath, { json: body })
|
|
310
|
+
.json<WebSearchResponse>();
|
|
311
|
+
return normalizeWebSearchResponse(resp);
|
|
312
|
+
}
|
|
223
313
|
}
|
|
224
314
|
|
|
225
315
|
// ============================================================================
|
|
@@ -254,6 +344,13 @@ function normalizeAddResult(raw: any): AddResult {
|
|
|
254
344
|
};
|
|
255
345
|
}
|
|
256
346
|
|
|
347
|
+
function normalizeWebSearchResponse(raw: any): WebSearchResponse {
|
|
348
|
+
return {
|
|
349
|
+
results: Array.isArray(raw?.results) ? raw.results : [],
|
|
350
|
+
total_results: typeof raw?.total_results === "number" ? raw.total_results : 0,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
257
354
|
// ============================================================================
|
|
258
355
|
// Document Context Builder
|
|
259
356
|
// ============================================================================
|
|
@@ -302,6 +399,19 @@ function buildDocumentContext(
|
|
|
302
399
|
return parts.join("\n\n");
|
|
303
400
|
}
|
|
304
401
|
|
|
402
|
+
function buildWebSearchContext(results: WebSearchResult[]): string {
|
|
403
|
+
return results
|
|
404
|
+
.map((result, index) => {
|
|
405
|
+
const parts = [`${index + 1}. ${result.title ?? result.url ?? "Untitled result"}`];
|
|
406
|
+
if (result.url) parts.push(`URL: ${result.url}`);
|
|
407
|
+
if (result.summary) parts.push(`Summary: ${result.summary}`);
|
|
408
|
+
if (result.source) parts.push(`Source: ${result.source}`);
|
|
409
|
+
if (result.published_date) parts.push(`Published: ${result.published_date}`);
|
|
410
|
+
return parts.join("\n");
|
|
411
|
+
})
|
|
412
|
+
.join("\n\n");
|
|
413
|
+
}
|
|
414
|
+
|
|
305
415
|
// ============================================================================
|
|
306
416
|
// Config Parser
|
|
307
417
|
// ============================================================================
|
|
@@ -320,6 +430,10 @@ const ALLOWED_KEYS = [
|
|
|
320
430
|
"searchThreshold",
|
|
321
431
|
"topK",
|
|
322
432
|
"rerank",
|
|
433
|
+
"webSearchIncludeDomains",
|
|
434
|
+
"webSearchExcludeDomains",
|
|
435
|
+
"webSearchCountry",
|
|
436
|
+
"webSearchTimezone",
|
|
323
437
|
];
|
|
324
438
|
|
|
325
439
|
function assertAllowedKeys(
|
|
@@ -332,6 +446,28 @@ function assertAllowedKeys(
|
|
|
332
446
|
throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`);
|
|
333
447
|
}
|
|
334
448
|
|
|
449
|
+
function parseOptionalStringArray(
|
|
450
|
+
value: unknown,
|
|
451
|
+
label: string,
|
|
452
|
+
): string[] | undefined {
|
|
453
|
+
if (value == null) return undefined;
|
|
454
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
455
|
+
throw new Error(`${label} must be an array of strings`);
|
|
456
|
+
}
|
|
457
|
+
return value;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function parseOptionalString(
|
|
461
|
+
value: unknown,
|
|
462
|
+
label: string,
|
|
463
|
+
): string | undefined {
|
|
464
|
+
if (value == null) return undefined;
|
|
465
|
+
if (typeof value !== "string") {
|
|
466
|
+
throw new Error(`${label} must be a string`);
|
|
467
|
+
}
|
|
468
|
+
return value;
|
|
469
|
+
}
|
|
470
|
+
|
|
335
471
|
const memoryLakeConfigSchema = {
|
|
336
472
|
parse(value: unknown): MemoryLakeConfig {
|
|
337
473
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -361,6 +497,22 @@ const memoryLakeConfigSchema = {
|
|
|
361
497
|
typeof cfg.searchThreshold === "number" ? cfg.searchThreshold : 0.3,
|
|
362
498
|
topK: typeof cfg.topK === "number" ? cfg.topK : 5,
|
|
363
499
|
rerank: cfg.rerank !== false,
|
|
500
|
+
webSearchIncludeDomains: parseOptionalStringArray(
|
|
501
|
+
cfg.webSearchIncludeDomains,
|
|
502
|
+
"webSearchIncludeDomains",
|
|
503
|
+
),
|
|
504
|
+
webSearchExcludeDomains: parseOptionalStringArray(
|
|
505
|
+
cfg.webSearchExcludeDomains,
|
|
506
|
+
"webSearchExcludeDomains",
|
|
507
|
+
),
|
|
508
|
+
webSearchCountry: parseOptionalString(
|
|
509
|
+
cfg.webSearchCountry,
|
|
510
|
+
"webSearchCountry",
|
|
511
|
+
),
|
|
512
|
+
webSearchTimezone: parseOptionalString(
|
|
513
|
+
cfg.webSearchTimezone,
|
|
514
|
+
"webSearchTimezone",
|
|
515
|
+
),
|
|
364
516
|
};
|
|
365
517
|
},
|
|
366
518
|
};
|
|
@@ -380,6 +532,52 @@ const memoryPlugin = {
|
|
|
380
532
|
const cfg = memoryLakeConfigSchema.parse(api.pluginConfig);
|
|
381
533
|
const provider: MemoryLakeProvider = new PlatformProvider(cfg.host, cfg.apiKey, cfg.projectId);
|
|
382
534
|
|
|
535
|
+
// Provider cache: avoids re-creating providers for the same host+apiKey+projectId
|
|
536
|
+
const providerCache = new Map<string, MemoryLakeProvider>();
|
|
537
|
+
const globalProviderKey = `${cfg.host}|${cfg.apiKey}|${cfg.projectId}`;
|
|
538
|
+
providerCache.set(globalProviderKey, provider);
|
|
539
|
+
|
|
540
|
+
function getProvider(effectiveCfg: MemoryLakeConfig): MemoryLakeProvider {
|
|
541
|
+
const key = `${effectiveCfg.host}|${effectiveCfg.apiKey}|${effectiveCfg.projectId}`;
|
|
542
|
+
let p = providerCache.get(key);
|
|
543
|
+
if (!p) {
|
|
544
|
+
p = new PlatformProvider(effectiveCfg.host, effectiveCfg.apiKey, effectiveCfg.projectId);
|
|
545
|
+
providerCache.set(key, p);
|
|
546
|
+
}
|
|
547
|
+
return p;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function resolveConfig(ctx: any): MemoryLakeConfig {
|
|
551
|
+
const workspaceDir = ctx?.workspaceDir;
|
|
552
|
+
if (!workspaceDir) return cfg;
|
|
553
|
+
|
|
554
|
+
const localPath = path.join(workspaceDir, ".memorylake", "config.json");
|
|
555
|
+
if (!fs.existsSync(localPath)) return cfg;
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
const raw = JSON.parse(fs.readFileSync(localPath, "utf-8"));
|
|
559
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
560
|
+
api.logger.warn(
|
|
561
|
+
`memorylake-openclaw: workspace config exists but is not a JSON object; falling back to global config (path: ${localPath})`,
|
|
562
|
+
);
|
|
563
|
+
return cfg;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const allowed = new Set(ALLOWED_KEYS);
|
|
567
|
+
const overrides: Record<string, unknown> = {};
|
|
568
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
569
|
+
if (allowed.has(key)) overrides[key] = value;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return { ...cfg, ...overrides } as MemoryLakeConfig;
|
|
573
|
+
} catch {
|
|
574
|
+
api.logger.warn(
|
|
575
|
+
`memorylake-openclaw: failed to parse workspace config JSON; falling back to global config (path: ${localPath})`,
|
|
576
|
+
);
|
|
577
|
+
return cfg;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
383
581
|
// Track current session ID for tool-level session scoping
|
|
384
582
|
let currentSessionId: string | undefined;
|
|
385
583
|
|
|
@@ -388,9 +586,9 @@ const memoryPlugin = {
|
|
|
388
586
|
);
|
|
389
587
|
|
|
390
588
|
// Helper: build add options
|
|
391
|
-
function buildAddOptions(userIdOverride?: string, sessionId?: string): AddOptions {
|
|
589
|
+
function buildAddOptions(effectiveCfg: MemoryLakeConfig, userIdOverride?: string, sessionId?: string): AddOptions {
|
|
392
590
|
const opts: AddOptions = {
|
|
393
|
-
user_id: userIdOverride ||
|
|
591
|
+
user_id: userIdOverride || effectiveCfg.userId,
|
|
394
592
|
infer: true,
|
|
395
593
|
metadata: { source: "OPENCLAW" },
|
|
396
594
|
};
|
|
@@ -400,14 +598,15 @@ const memoryPlugin = {
|
|
|
400
598
|
|
|
401
599
|
// Helper: build search options
|
|
402
600
|
function buildSearchOptions(
|
|
601
|
+
effectiveCfg: MemoryLakeConfig,
|
|
403
602
|
userIdOverride?: string,
|
|
404
603
|
limit?: number,
|
|
405
604
|
): SearchOptions {
|
|
406
605
|
return {
|
|
407
|
-
user_id: userIdOverride ||
|
|
408
|
-
top_k: limit ??
|
|
409
|
-
threshold:
|
|
410
|
-
rerank:
|
|
606
|
+
user_id: userIdOverride || effectiveCfg.userId,
|
|
607
|
+
top_k: limit ?? effectiveCfg.topK,
|
|
608
|
+
threshold: effectiveCfg.searchThreshold,
|
|
609
|
+
rerank: effectiveCfg.rerank,
|
|
411
610
|
};
|
|
412
611
|
}
|
|
413
612
|
|
|
@@ -416,7 +615,7 @@ const memoryPlugin = {
|
|
|
416
615
|
// ========================================================================
|
|
417
616
|
|
|
418
617
|
api.registerTool(
|
|
419
|
-
{
|
|
618
|
+
(ctx) => ({
|
|
420
619
|
name: "memory_search",
|
|
421
620
|
label: "Memory Search",
|
|
422
621
|
description:
|
|
@@ -446,6 +645,8 @@ const memoryPlugin = {
|
|
|
446
645
|
),
|
|
447
646
|
}),
|
|
448
647
|
async execute(_toolCallId, params) {
|
|
648
|
+
const effectiveCfg = resolveConfig(ctx);
|
|
649
|
+
const effectiveProvider = getProvider(effectiveCfg);
|
|
449
650
|
const { query, limit, userId, scope = "all" } = params as {
|
|
450
651
|
query: string;
|
|
451
652
|
limit?: number;
|
|
@@ -454,9 +655,9 @@ const memoryPlugin = {
|
|
|
454
655
|
};
|
|
455
656
|
|
|
456
657
|
try {
|
|
457
|
-
const results = await
|
|
658
|
+
const results = await effectiveProvider.search(
|
|
458
659
|
query,
|
|
459
|
-
buildSearchOptions(userId, limit),
|
|
660
|
+
buildSearchOptions(effectiveCfg, userId, limit),
|
|
460
661
|
);
|
|
461
662
|
|
|
462
663
|
if (!results || results.length === 0) {
|
|
@@ -502,12 +703,12 @@ const memoryPlugin = {
|
|
|
502
703
|
};
|
|
503
704
|
}
|
|
504
705
|
},
|
|
505
|
-
},
|
|
706
|
+
}),
|
|
506
707
|
{ name: "memory_search" },
|
|
507
708
|
);
|
|
508
709
|
|
|
509
710
|
api.registerTool(
|
|
510
|
-
{
|
|
711
|
+
(ctx) => ({
|
|
511
712
|
name: "memory_store",
|
|
512
713
|
label: "Memory Store",
|
|
513
714
|
description:
|
|
@@ -526,6 +727,8 @@ const memoryPlugin = {
|
|
|
526
727
|
),
|
|
527
728
|
}),
|
|
528
729
|
async execute(_toolCallId, params) {
|
|
730
|
+
const effectiveCfg = resolveConfig(ctx);
|
|
731
|
+
const effectiveProvider = getProvider(effectiveCfg);
|
|
529
732
|
const { text, userId } = params as {
|
|
530
733
|
text: string;
|
|
531
734
|
userId?: string;
|
|
@@ -533,9 +736,9 @@ const memoryPlugin = {
|
|
|
533
736
|
};
|
|
534
737
|
|
|
535
738
|
try {
|
|
536
|
-
const result = await
|
|
739
|
+
const result = await effectiveProvider.add(
|
|
537
740
|
[{ role: "user", content: text }],
|
|
538
|
-
buildAddOptions(userId, currentSessionId),
|
|
741
|
+
buildAddOptions(effectiveCfg, userId, currentSessionId),
|
|
539
742
|
);
|
|
540
743
|
|
|
541
744
|
const count = result.results?.length ?? 0;
|
|
@@ -566,12 +769,12 @@ const memoryPlugin = {
|
|
|
566
769
|
};
|
|
567
770
|
}
|
|
568
771
|
},
|
|
569
|
-
},
|
|
772
|
+
}),
|
|
570
773
|
{ name: "memory_store" },
|
|
571
774
|
);
|
|
572
775
|
|
|
573
776
|
api.registerTool(
|
|
574
|
-
{
|
|
777
|
+
(ctx) => ({
|
|
575
778
|
name: "memory_get",
|
|
576
779
|
label: "Memory Get",
|
|
577
780
|
description: "Retrieve a specific memory by its ID from MemoryLake.",
|
|
@@ -579,10 +782,12 @@ const memoryPlugin = {
|
|
|
579
782
|
memoryId: Type.String({ description: "The memory ID to retrieve" }),
|
|
580
783
|
}),
|
|
581
784
|
async execute(_toolCallId, params) {
|
|
785
|
+
const effectiveCfg = resolveConfig(ctx);
|
|
786
|
+
const effectiveProvider = getProvider(effectiveCfg);
|
|
582
787
|
const { memoryId } = params as { memoryId: string };
|
|
583
788
|
|
|
584
789
|
try {
|
|
585
|
-
const memory = await
|
|
790
|
+
const memory = await effectiveProvider.get(memoryId);
|
|
586
791
|
|
|
587
792
|
return {
|
|
588
793
|
content: [
|
|
@@ -605,12 +810,12 @@ const memoryPlugin = {
|
|
|
605
810
|
};
|
|
606
811
|
}
|
|
607
812
|
},
|
|
608
|
-
},
|
|
813
|
+
}),
|
|
609
814
|
{ name: "memory_get" },
|
|
610
815
|
);
|
|
611
816
|
|
|
612
817
|
api.registerTool(
|
|
613
|
-
{
|
|
818
|
+
(ctx) => ({
|
|
614
819
|
name: "memory_list",
|
|
615
820
|
label: "Memory List",
|
|
616
821
|
description:
|
|
@@ -634,11 +839,13 @@ const memoryPlugin = {
|
|
|
634
839
|
),
|
|
635
840
|
}),
|
|
636
841
|
async execute(_toolCallId, params) {
|
|
842
|
+
const effectiveCfg = resolveConfig(ctx);
|
|
843
|
+
const effectiveProvider = getProvider(effectiveCfg);
|
|
637
844
|
const { userId, scope = "all" } = params as { userId?: string; scope?: "session" | "long-term" | "all" };
|
|
638
845
|
|
|
639
846
|
try {
|
|
640
|
-
const uid = userId ||
|
|
641
|
-
const memories = await
|
|
847
|
+
const uid = userId || effectiveCfg.userId;
|
|
848
|
+
const memories = await effectiveProvider.getAll({ user_id: uid });
|
|
642
849
|
|
|
643
850
|
if (!memories || memories.length === 0) {
|
|
644
851
|
return {
|
|
@@ -683,12 +890,12 @@ const memoryPlugin = {
|
|
|
683
890
|
};
|
|
684
891
|
}
|
|
685
892
|
},
|
|
686
|
-
},
|
|
893
|
+
}),
|
|
687
894
|
{ name: "memory_list" },
|
|
688
895
|
);
|
|
689
896
|
|
|
690
897
|
api.registerTool(
|
|
691
|
-
{
|
|
898
|
+
(ctx) => ({
|
|
692
899
|
name: "memory_forget",
|
|
693
900
|
label: "Memory Forget",
|
|
694
901
|
description:
|
|
@@ -697,10 +904,12 @@ const memoryPlugin = {
|
|
|
697
904
|
memoryId: Type.String({ description: "Memory ID to delete" }),
|
|
698
905
|
}),
|
|
699
906
|
async execute(_toolCallId, params) {
|
|
907
|
+
const effectiveCfg = resolveConfig(ctx);
|
|
908
|
+
const effectiveProvider = getProvider(effectiveCfg);
|
|
700
909
|
const { memoryId } = params as { memoryId: string };
|
|
701
910
|
|
|
702
911
|
try {
|
|
703
|
-
await
|
|
912
|
+
await effectiveProvider.delete(memoryId);
|
|
704
913
|
return {
|
|
705
914
|
content: [
|
|
706
915
|
{ type: "text", text: `Memory ${memoryId} forgotten.` },
|
|
@@ -719,12 +928,12 @@ const memoryPlugin = {
|
|
|
719
928
|
};
|
|
720
929
|
}
|
|
721
930
|
},
|
|
722
|
-
},
|
|
931
|
+
}),
|
|
723
932
|
{ name: "memory_forget" },
|
|
724
933
|
);
|
|
725
934
|
|
|
726
935
|
api.registerTool(
|
|
727
|
-
{
|
|
936
|
+
(ctx) => ({
|
|
728
937
|
name: "document_search",
|
|
729
938
|
label: "Document Search",
|
|
730
939
|
description:
|
|
@@ -739,12 +948,14 @@ const memoryPlugin = {
|
|
|
739
948
|
),
|
|
740
949
|
}),
|
|
741
950
|
async execute(_toolCallId, params) {
|
|
951
|
+
const effectiveCfg = resolveConfig(ctx);
|
|
952
|
+
const effectiveProvider = getProvider(effectiveCfg);
|
|
742
953
|
const { query, topN } = params as { query: string; topN?: number };
|
|
743
954
|
|
|
744
955
|
try {
|
|
745
|
-
const response = await
|
|
956
|
+
const response = await effectiveProvider.searchDocuments(
|
|
746
957
|
query,
|
|
747
|
-
topN ??
|
|
958
|
+
topN ?? effectiveCfg.topK,
|
|
748
959
|
);
|
|
749
960
|
|
|
750
961
|
if (!response.results || response.results.length === 0) {
|
|
@@ -779,10 +990,140 @@ const memoryPlugin = {
|
|
|
779
990
|
};
|
|
780
991
|
}
|
|
781
992
|
},
|
|
782
|
-
},
|
|
993
|
+
}),
|
|
783
994
|
{ name: "document_search" },
|
|
784
995
|
);
|
|
785
996
|
|
|
997
|
+
api.registerTool(
|
|
998
|
+
(ctx) => ({
|
|
999
|
+
name: "advanced_web_search",
|
|
1000
|
+
label: "Advanced Web Search",
|
|
1001
|
+
description:
|
|
1002
|
+
"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.",
|
|
1003
|
+
parameters: Type.Object({
|
|
1004
|
+
query: Type.String({
|
|
1005
|
+
description:
|
|
1006
|
+
"The web search query to send to the unified search endpoint.",
|
|
1007
|
+
}),
|
|
1008
|
+
domain: Type.Optional(
|
|
1009
|
+
Type.Union(
|
|
1010
|
+
[
|
|
1011
|
+
Type.Literal("web"),
|
|
1012
|
+
Type.Literal("academic"),
|
|
1013
|
+
Type.Literal("news"),
|
|
1014
|
+
Type.Literal("people"),
|
|
1015
|
+
Type.Literal("company"),
|
|
1016
|
+
Type.Literal("financial"),
|
|
1017
|
+
Type.Literal("markets"),
|
|
1018
|
+
Type.Literal("code"),
|
|
1019
|
+
Type.Literal("legal"),
|
|
1020
|
+
Type.Literal("government"),
|
|
1021
|
+
Type.Literal("poi"),
|
|
1022
|
+
Type.Literal("auto"),
|
|
1023
|
+
],
|
|
1024
|
+
{
|
|
1025
|
+
description:
|
|
1026
|
+
"Search domain. Default: web. Invalid or unknown values are treated as auto.",
|
|
1027
|
+
},
|
|
1028
|
+
),
|
|
1029
|
+
),
|
|
1030
|
+
maxResults: Type.Optional(
|
|
1031
|
+
Type.Number({
|
|
1032
|
+
description: `Maximum number of web results to return (default: ${cfg.topK}).`,
|
|
1033
|
+
minimum: 1,
|
|
1034
|
+
}),
|
|
1035
|
+
),
|
|
1036
|
+
startDate: Type.Optional(
|
|
1037
|
+
Type.String({
|
|
1038
|
+
description:
|
|
1039
|
+
"Only include results published on or after this date (YYYY-MM-DD).",
|
|
1040
|
+
}),
|
|
1041
|
+
),
|
|
1042
|
+
endDate: Type.Optional(
|
|
1043
|
+
Type.String({
|
|
1044
|
+
description:
|
|
1045
|
+
"Only include results published on or before this date (YYYY-MM-DD).",
|
|
1046
|
+
}),
|
|
1047
|
+
),
|
|
1048
|
+
}),
|
|
1049
|
+
async execute(_toolCallId, params) {
|
|
1050
|
+
const effectiveCfg = resolveConfig(ctx);
|
|
1051
|
+
const effectiveProvider = getProvider(effectiveCfg);
|
|
1052
|
+
const {
|
|
1053
|
+
query,
|
|
1054
|
+
domain: rawDomain,
|
|
1055
|
+
maxResults,
|
|
1056
|
+
startDate,
|
|
1057
|
+
endDate,
|
|
1058
|
+
} = params as {
|
|
1059
|
+
query: string;
|
|
1060
|
+
domain?: string;
|
|
1061
|
+
maxResults?: number;
|
|
1062
|
+
startDate?: string;
|
|
1063
|
+
endDate?: string;
|
|
1064
|
+
};
|
|
1065
|
+
const domain: WebSearchDomain =
|
|
1066
|
+
rawDomain === undefined || rawDomain === null
|
|
1067
|
+
? "web"
|
|
1068
|
+
: normalizeWebSearchDomain(rawDomain);
|
|
1069
|
+
|
|
1070
|
+
try {
|
|
1071
|
+
const response = await effectiveProvider.searchWeb(query, {
|
|
1072
|
+
domain,
|
|
1073
|
+
max_results: maxResults ?? effectiveCfg.topK,
|
|
1074
|
+
start_date: startDate,
|
|
1075
|
+
end_date: endDate,
|
|
1076
|
+
include_domains: effectiveCfg.webSearchIncludeDomains,
|
|
1077
|
+
exclude_domains: effectiveCfg.webSearchExcludeDomains,
|
|
1078
|
+
user_location:
|
|
1079
|
+
effectiveCfg.webSearchCountry || effectiveCfg.webSearchTimezone
|
|
1080
|
+
? {
|
|
1081
|
+
country: effectiveCfg.webSearchCountry,
|
|
1082
|
+
timezone: effectiveCfg.webSearchTimezone,
|
|
1083
|
+
}
|
|
1084
|
+
: undefined,
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
if (!response.results || response.results.length === 0) {
|
|
1088
|
+
return {
|
|
1089
|
+
content: [
|
|
1090
|
+
{ type: "text", text: "No relevant web results found." },
|
|
1091
|
+
],
|
|
1092
|
+
details: { count: 0, total_results: response.total_results },
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const context = buildWebSearchContext(response.results);
|
|
1097
|
+
|
|
1098
|
+
return {
|
|
1099
|
+
content: [
|
|
1100
|
+
{
|
|
1101
|
+
type: "text",
|
|
1102
|
+
text: `Found ${response.results.length} web results:\n\n${context}`,
|
|
1103
|
+
},
|
|
1104
|
+
],
|
|
1105
|
+
details: {
|
|
1106
|
+
count: response.results.length,
|
|
1107
|
+
total_results: response.total_results,
|
|
1108
|
+
results: response.results,
|
|
1109
|
+
},
|
|
1110
|
+
};
|
|
1111
|
+
} catch (err) {
|
|
1112
|
+
return {
|
|
1113
|
+
content: [
|
|
1114
|
+
{
|
|
1115
|
+
type: "text",
|
|
1116
|
+
text: `Web search failed: ${String(err)}`,
|
|
1117
|
+
},
|
|
1118
|
+
],
|
|
1119
|
+
details: { error: String(err) },
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
}),
|
|
1124
|
+
{ optional: true },
|
|
1125
|
+
);
|
|
1126
|
+
|
|
786
1127
|
// ========================================================================
|
|
787
1128
|
// CLI Commands
|
|
788
1129
|
// ========================================================================
|
|
@@ -803,7 +1144,7 @@ const memoryPlugin = {
|
|
|
803
1144
|
const limit = parseInt(opts.limit, 10);
|
|
804
1145
|
const results = await provider.search(
|
|
805
1146
|
query,
|
|
806
|
-
buildSearchOptions(undefined, limit),
|
|
1147
|
+
buildSearchOptions(cfg, undefined, limit),
|
|
807
1148
|
);
|
|
808
1149
|
|
|
809
1150
|
if (!results.length) {
|
|
@@ -855,13 +1196,17 @@ const memoryPlugin = {
|
|
|
855
1196
|
api.on("before_agent_start", async (event, ctx) => {
|
|
856
1197
|
if (!event.prompt || event.prompt.length < 5) return;
|
|
857
1198
|
|
|
1199
|
+
// Resolve per-workspace config override
|
|
1200
|
+
const effectiveCfg = resolveConfig(ctx);
|
|
1201
|
+
const effectiveProvider = getProvider(effectiveCfg);
|
|
1202
|
+
|
|
858
1203
|
// Track session ID
|
|
859
1204
|
const sessionId = (ctx as any)?.sessionKey ?? undefined;
|
|
860
1205
|
if (sessionId) currentSessionId = sessionId;
|
|
861
1206
|
|
|
862
1207
|
const [memoryResult, docResult] = await Promise.allSettled([
|
|
863
|
-
|
|
864
|
-
|
|
1208
|
+
effectiveProvider.search(event.prompt, buildSearchOptions(effectiveCfg)),
|
|
1209
|
+
effectiveProvider.searchDocuments(event.prompt, effectiveCfg.topK),
|
|
865
1210
|
]);
|
|
866
1211
|
|
|
867
1212
|
const contextParts: string[] = [];
|
|
@@ -905,6 +1250,10 @@ const memoryPlugin = {
|
|
|
905
1250
|
return;
|
|
906
1251
|
}
|
|
907
1252
|
|
|
1253
|
+
// Resolve per-workspace config override
|
|
1254
|
+
const effectiveCfg = resolveConfig(ctx);
|
|
1255
|
+
const effectiveProvider = getProvider(effectiveCfg);
|
|
1256
|
+
|
|
908
1257
|
// Track session ID
|
|
909
1258
|
const sessionId = (ctx as any)?.sessionKey ?? undefined;
|
|
910
1259
|
if (sessionId) currentSessionId = sessionId;
|
|
@@ -962,8 +1311,8 @@ const memoryPlugin = {
|
|
|
962
1311
|
|
|
963
1312
|
if (formattedMessages.length === 0) return;
|
|
964
1313
|
|
|
965
|
-
const addOpts = buildAddOptions(undefined, currentSessionId);
|
|
966
|
-
const result = await
|
|
1314
|
+
const addOpts = buildAddOptions(effectiveCfg, undefined, currentSessionId);
|
|
1315
|
+
const result = await effectiveProvider.add(
|
|
967
1316
|
formattedMessages,
|
|
968
1317
|
addOpts,
|
|
969
1318
|
);
|
package/openclaw.plugin.json
CHANGED
|
@@ -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
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-memorylake-config
|
|
3
|
+
description: Use when the user asks to configure agent-specific memorylake properties (e.g. projectId) for the current agent. Writes to the agent-specific config file which overrides the global config.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agent MemoryLake Config
|
|
7
|
+
|
|
8
|
+
Configure agent-specific memorylake properties for the current agent. The config file is located at `{workspace}/.memorylake/config.json`, where `{workspace}` is the agent's workspace directory. This config overrides corresponding properties from the global config.
|
|
9
|
+
|
|
10
|
+
## Step 1 — Confirm projectId
|
|
11
|
+
|
|
12
|
+
Ask the user for the `projectId` to configure.
|
|
13
|
+
|
|
14
|
+
If the user has already provided the `projectId` in their message, skip the question and proceed directly.
|
|
15
|
+
|
|
16
|
+
## Step 2 — Write Config
|
|
17
|
+
|
|
18
|
+
1. Ensure the `.memorylake/` directory exists inside the agent's workspace directory:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd {workspace}
|
|
22
|
+
mkdir -p .memorylake
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
2. If `.memorylake/config.json` already exists, read it first and merge the new properties into the existing config. Do NOT overwrite properties the user did not mention.
|
|
26
|
+
|
|
27
|
+
3. If `.memorylake/config.json` does not exist, create it with the provided properties.
|
|
28
|
+
|
|
29
|
+
4. Write the config file. Example format:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"projectId": "xxx"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Step 3 — Confirm Result
|
|
38
|
+
|
|
39
|
+
Read the written `.memorylake/config.json` and confirm to the user that the configuration is complete.
|
|
40
|
+
|
|
41
|
+
## Common Mistakes
|
|
42
|
+
|
|
43
|
+
- Do NOT overwrite existing properties that the user did not mention — always merge
|