neuralmemory 1.5.0
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/index.d.ts +30 -0
- package/dist/index.js +184 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-client.d.ts +43 -0
- package/dist/mcp-client.js +242 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/tools.d.ts +34 -0
- package/dist/tools.js +184 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/openclaw.plugin.json +93 -0
- package/package.json +52 -0
- package/src/index.ts +266 -0
- package/src/mcp-client.ts +322 -0
- package/src/tools.ts +218 -0
- package/src/types.ts +80 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NeuralMemory tool definitions for OpenClaw.
|
|
3
|
+
*
|
|
4
|
+
* Each tool proxies to the MCP server via JSON-RPC.
|
|
5
|
+
*
|
|
6
|
+
* Uses raw JSON Schema for parameters. Provider compatibility notes:
|
|
7
|
+
* - `additionalProperties: false` required by OpenAI strict mode
|
|
8
|
+
* - `number` instead of `integer` for Gemini compatibility
|
|
9
|
+
* - No `maxLength`/`maxItems`/`minimum`/`maximum` — some providers
|
|
10
|
+
* reject schemas with constraint keywords; our MCP server validates
|
|
11
|
+
*
|
|
12
|
+
* Registers 6 core tools:
|
|
13
|
+
* nmem_remember — Store a memory
|
|
14
|
+
* nmem_recall — Query/search memories
|
|
15
|
+
* nmem_context — Get recent context
|
|
16
|
+
* nmem_todo — Quick TODO shortcut
|
|
17
|
+
* nmem_stats — Brain statistics
|
|
18
|
+
* nmem_health — Brain health diagnostics
|
|
19
|
+
*/
|
|
20
|
+
// ── Tool factory ───────────────────────────────────────────
|
|
21
|
+
export function createTools(mcp) {
|
|
22
|
+
const call = async (toolName, args) => {
|
|
23
|
+
if (!mcp.connected) {
|
|
24
|
+
return {
|
|
25
|
+
error: true,
|
|
26
|
+
message: "NeuralMemory service not running. Start the service first.",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const raw = await mcp.callTool(toolName, args);
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(raw);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { text: raw };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return {
|
|
40
|
+
error: true,
|
|
41
|
+
message: `Tool ${toolName} failed: ${err.message}`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return [
|
|
46
|
+
{
|
|
47
|
+
name: "nmem_remember",
|
|
48
|
+
description: "Store a memory in NeuralMemory. Use this to remember facts, decisions, " +
|
|
49
|
+
"insights, todos, errors, and other information that should persist across sessions.",
|
|
50
|
+
parameters: {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
content: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "The content to remember",
|
|
56
|
+
},
|
|
57
|
+
type: {
|
|
58
|
+
type: "string",
|
|
59
|
+
enum: [
|
|
60
|
+
"fact",
|
|
61
|
+
"decision",
|
|
62
|
+
"preference",
|
|
63
|
+
"todo",
|
|
64
|
+
"insight",
|
|
65
|
+
"context",
|
|
66
|
+
"instruction",
|
|
67
|
+
"error",
|
|
68
|
+
"workflow",
|
|
69
|
+
"reference",
|
|
70
|
+
],
|
|
71
|
+
description: "Memory type (auto-detected if not specified)",
|
|
72
|
+
},
|
|
73
|
+
priority: {
|
|
74
|
+
type: "number",
|
|
75
|
+
description: "Priority 0-10 (5=normal, 10=critical)",
|
|
76
|
+
},
|
|
77
|
+
tags: {
|
|
78
|
+
type: "array",
|
|
79
|
+
items: { type: "string" },
|
|
80
|
+
description: "Tags for categorization",
|
|
81
|
+
},
|
|
82
|
+
expires_days: {
|
|
83
|
+
type: "number",
|
|
84
|
+
description: "Days until memory expires (1-3650)",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ["content"],
|
|
88
|
+
additionalProperties: false,
|
|
89
|
+
},
|
|
90
|
+
execute: (_id, args) => call("nmem_remember", args),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "nmem_recall",
|
|
94
|
+
description: "Query memories from NeuralMemory. Use this to recall past information, " +
|
|
95
|
+
"decisions, patterns, or context relevant to the current task.",
|
|
96
|
+
parameters: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {
|
|
99
|
+
query: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "The query to search memories",
|
|
102
|
+
},
|
|
103
|
+
depth: {
|
|
104
|
+
type: "number",
|
|
105
|
+
description: "Search depth: 0=instant, 1=context, 2=habit, 3=deep",
|
|
106
|
+
},
|
|
107
|
+
max_tokens: {
|
|
108
|
+
type: "number",
|
|
109
|
+
description: "Maximum tokens in response (default: 500)",
|
|
110
|
+
},
|
|
111
|
+
min_confidence: {
|
|
112
|
+
type: "number",
|
|
113
|
+
description: "Minimum confidence threshold (0-1)",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
required: ["query"],
|
|
117
|
+
additionalProperties: false,
|
|
118
|
+
},
|
|
119
|
+
execute: (_id, args) => call("nmem_recall", args),
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "nmem_context",
|
|
123
|
+
description: "Get recent context from NeuralMemory. Use this at the start of " +
|
|
124
|
+
"tasks to inject relevant recent memories.",
|
|
125
|
+
parameters: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
limit: {
|
|
129
|
+
type: "number",
|
|
130
|
+
description: "Number of recent memories (default: 10, max: 200)",
|
|
131
|
+
},
|
|
132
|
+
fresh_only: {
|
|
133
|
+
type: "boolean",
|
|
134
|
+
description: "Only include memories less than 30 days old",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
additionalProperties: false,
|
|
138
|
+
},
|
|
139
|
+
execute: (_id, args) => call("nmem_context", args),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "nmem_todo",
|
|
143
|
+
description: "Quick shortcut to add a TODO memory with 30-day expiry.",
|
|
144
|
+
parameters: {
|
|
145
|
+
type: "object",
|
|
146
|
+
properties: {
|
|
147
|
+
task: {
|
|
148
|
+
type: "string",
|
|
149
|
+
description: "The task to remember",
|
|
150
|
+
},
|
|
151
|
+
priority: {
|
|
152
|
+
type: "number",
|
|
153
|
+
description: "Priority 0-10 (default: 5)",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
required: ["task"],
|
|
157
|
+
additionalProperties: false,
|
|
158
|
+
},
|
|
159
|
+
execute: (_id, args) => call("nmem_todo", args),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "nmem_stats",
|
|
163
|
+
description: "Get brain statistics including memory counts and freshness.",
|
|
164
|
+
parameters: {
|
|
165
|
+
type: "object",
|
|
166
|
+
properties: {},
|
|
167
|
+
additionalProperties: false,
|
|
168
|
+
},
|
|
169
|
+
execute: (_id, args) => call("nmem_stats", args),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "nmem_health",
|
|
173
|
+
description: "Get brain health diagnostics including grade, purity score, " +
|
|
174
|
+
"and recommendations.",
|
|
175
|
+
parameters: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {},
|
|
178
|
+
additionalProperties: false,
|
|
179
|
+
},
|
|
180
|
+
execute: (_id, args) => call("nmem_health", args),
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal OpenClaw plugin types for standalone compilation.
|
|
3
|
+
* At runtime, jiti resolves the full types from OpenClaw's codebase.
|
|
4
|
+
*/
|
|
5
|
+
export type PluginLogger = {
|
|
6
|
+
debug?: (message: string) => void;
|
|
7
|
+
info: (message: string) => void;
|
|
8
|
+
warn: (message: string) => void;
|
|
9
|
+
error: (message: string) => void;
|
|
10
|
+
};
|
|
11
|
+
export type PluginKind = "memory";
|
|
12
|
+
export type OpenClawPluginServiceContext = {
|
|
13
|
+
config: unknown;
|
|
14
|
+
workspaceDir?: string;
|
|
15
|
+
stateDir: string;
|
|
16
|
+
logger: PluginLogger;
|
|
17
|
+
};
|
|
18
|
+
export type OpenClawPluginService = {
|
|
19
|
+
id: string;
|
|
20
|
+
start: (ctx: OpenClawPluginServiceContext) => void | Promise<void>;
|
|
21
|
+
stop?: (ctx: OpenClawPluginServiceContext) => void | Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
export type BeforeAgentStartEvent = {
|
|
24
|
+
prompt: string;
|
|
25
|
+
messages?: unknown[];
|
|
26
|
+
};
|
|
27
|
+
export type BeforeAgentStartResult = {
|
|
28
|
+
systemPrompt?: string;
|
|
29
|
+
prependContext?: string;
|
|
30
|
+
modelOverride?: string;
|
|
31
|
+
providerOverride?: string;
|
|
32
|
+
};
|
|
33
|
+
export type AgentEndEvent = {
|
|
34
|
+
messages: unknown[];
|
|
35
|
+
success: boolean;
|
|
36
|
+
error?: string;
|
|
37
|
+
durationMs?: number;
|
|
38
|
+
};
|
|
39
|
+
export type AgentContext = {
|
|
40
|
+
agentId?: string;
|
|
41
|
+
sessionKey?: string;
|
|
42
|
+
workspaceDir?: string;
|
|
43
|
+
};
|
|
44
|
+
export type OpenClawPluginApi = {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
config: unknown;
|
|
48
|
+
pluginConfig?: Record<string, unknown>;
|
|
49
|
+
runtime: unknown;
|
|
50
|
+
logger: PluginLogger;
|
|
51
|
+
registerTool: (tool: unknown, opts?: {
|
|
52
|
+
name?: string;
|
|
53
|
+
names?: string[];
|
|
54
|
+
}) => void;
|
|
55
|
+
registerService: (service: OpenClawPluginService) => void;
|
|
56
|
+
on: (hookName: string, handler: (...args: unknown[]) => unknown, opts?: {
|
|
57
|
+
priority?: number;
|
|
58
|
+
}) => void;
|
|
59
|
+
};
|
|
60
|
+
export type OpenClawPluginDefinition = {
|
|
61
|
+
id?: string;
|
|
62
|
+
name?: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
version?: string;
|
|
65
|
+
kind?: PluginKind;
|
|
66
|
+
register?: (api: OpenClawPluginApi) => void | Promise<void>;
|
|
67
|
+
activate?: (api: OpenClawPluginApi) => void | Promise<void>;
|
|
68
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "neuralmemory",
|
|
3
|
+
"kind": "memory",
|
|
4
|
+
"name": "NeuralMemory",
|
|
5
|
+
"description": "Brain-inspired persistent memory for AI agents — neurons, synapses, and fibers. REQUIRED: set plugins.slots.memory = \"neuralmemory\" in openclaw.json to disable the default memory-core plugin and activate NeuralMemory as the exclusive memory provider.",
|
|
6
|
+
"version": "1.5.0",
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"jsonSchema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": {
|
|
12
|
+
"pythonPath": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "Path to Python executable with neural-memory installed",
|
|
15
|
+
"default": "python"
|
|
16
|
+
},
|
|
17
|
+
"brain": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"pattern": "^[a-zA-Z0-9_\\-.]{1,64}$",
|
|
20
|
+
"description": "Brain name to use for this workspace",
|
|
21
|
+
"default": "default"
|
|
22
|
+
},
|
|
23
|
+
"autoContext": {
|
|
24
|
+
"type": "boolean",
|
|
25
|
+
"description": "Inject relevant memory context before each agent run",
|
|
26
|
+
"default": true
|
|
27
|
+
},
|
|
28
|
+
"autoCapture": {
|
|
29
|
+
"type": "boolean",
|
|
30
|
+
"description": "Auto-extract and store memories after each agent run",
|
|
31
|
+
"default": true
|
|
32
|
+
},
|
|
33
|
+
"contextDepth": {
|
|
34
|
+
"type": "integer",
|
|
35
|
+
"minimum": 0,
|
|
36
|
+
"maximum": 3,
|
|
37
|
+
"description": "Recall depth for auto-context: 0=instant, 1=context, 2=habit, 3=deep",
|
|
38
|
+
"default": 1
|
|
39
|
+
},
|
|
40
|
+
"maxContextTokens": {
|
|
41
|
+
"type": "integer",
|
|
42
|
+
"minimum": 100,
|
|
43
|
+
"maximum": 10000,
|
|
44
|
+
"description": "Maximum tokens for auto-context injection",
|
|
45
|
+
"default": 500
|
|
46
|
+
},
|
|
47
|
+
"timeout": {
|
|
48
|
+
"type": "integer",
|
|
49
|
+
"minimum": 5000,
|
|
50
|
+
"maximum": 120000,
|
|
51
|
+
"description": "MCP request timeout in milliseconds",
|
|
52
|
+
"default": 30000
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"uiHints": {
|
|
57
|
+
"_setup": {
|
|
58
|
+
"label": "Required Setup",
|
|
59
|
+
"help": "Add to openclaw.json: { \"plugins\": { \"slots\": { \"memory\": \"neuralmemory\" } } } — this disables the default memory-core plugin so agents use NeuralMemory exclusively. Without this, agents may still call memory_search (memory-core) even with AGENTS.MD rules."
|
|
60
|
+
},
|
|
61
|
+
"pythonPath": {
|
|
62
|
+
"label": "Python Path",
|
|
63
|
+
"placeholder": "python",
|
|
64
|
+
"help": "Path to Python executable that has neural-memory installed (pip install neural-memory)"
|
|
65
|
+
},
|
|
66
|
+
"brain": {
|
|
67
|
+
"label": "Brain Name",
|
|
68
|
+
"placeholder": "default",
|
|
69
|
+
"help": "Which brain to use — each workspace can have its own brain"
|
|
70
|
+
},
|
|
71
|
+
"autoContext": {
|
|
72
|
+
"label": "Auto-inject Context",
|
|
73
|
+
"help": "Automatically query relevant memories and inject them before each agent run"
|
|
74
|
+
},
|
|
75
|
+
"autoCapture": {
|
|
76
|
+
"label": "Auto-capture Memories",
|
|
77
|
+
"help": "Automatically extract facts, decisions, and insights after each agent run"
|
|
78
|
+
},
|
|
79
|
+
"contextDepth": {
|
|
80
|
+
"label": "Context Depth",
|
|
81
|
+
"help": "How deep to search: 0=instant lookup, 1=contextual, 2=habit patterns, 3=full graph traversal"
|
|
82
|
+
},
|
|
83
|
+
"maxContextTokens": {
|
|
84
|
+
"label": "Max Context Tokens",
|
|
85
|
+
"help": "Maximum tokens to inject as context (higher = more context but uses more prompt space)"
|
|
86
|
+
},
|
|
87
|
+
"timeout": {
|
|
88
|
+
"label": "MCP Timeout (ms)",
|
|
89
|
+
"help": "How long to wait for MCP server responses. Increase if you see timeout errors on slow machines (default: 30000)"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "neuralmemory",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "NeuralMemory plugin for OpenClaw — brain-inspired persistent memory for AI agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"openclaw": {
|
|
9
|
+
"id": "neuralmemory",
|
|
10
|
+
"extensions": [
|
|
11
|
+
"./dist/index.js"
|
|
12
|
+
],
|
|
13
|
+
"source": "./src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"postinstall": "npm run build 2>/dev/null || true",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"test:coverage": "vitest run --coverage"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"openclaw",
|
|
27
|
+
"openclaw-plugin",
|
|
28
|
+
"memory",
|
|
29
|
+
"neuralmemory",
|
|
30
|
+
"ai-agent",
|
|
31
|
+
"persistent-memory"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/pinkponk/NeuralMemory"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"src/",
|
|
40
|
+
"dist/",
|
|
41
|
+
"openclaw.plugin.json"
|
|
42
|
+
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^25.2.2",
|
|
48
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
49
|
+
"typescript": "^5.9.3",
|
|
50
|
+
"vitest": "^3.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NeuralMemory — OpenClaw Memory Plugin
|
|
3
|
+
*
|
|
4
|
+
* Brain-inspired persistent memory for AI agents.
|
|
5
|
+
* Occupies the exclusive "memory" plugin slot.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* OpenClaw ←→ Plugin (TypeScript) ←→ MCP stdio ←→ NeuralMemory (Python)
|
|
9
|
+
*
|
|
10
|
+
* Registers:
|
|
11
|
+
* 6 tools — nmem_remember, nmem_recall, nmem_context, nmem_todo, nmem_stats, nmem_health
|
|
12
|
+
* 1 service — MCP process lifecycle (start/stop)
|
|
13
|
+
* 2 hooks — before_agent_start (auto-context), agent_end (auto-capture)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type {
|
|
17
|
+
OpenClawPluginDefinition,
|
|
18
|
+
OpenClawPluginApi,
|
|
19
|
+
BeforeAgentStartEvent,
|
|
20
|
+
BeforeAgentStartResult,
|
|
21
|
+
AgentContext,
|
|
22
|
+
AgentEndEvent,
|
|
23
|
+
} from "./types.js";
|
|
24
|
+
import { NeuralMemoryMcpClient } from "./mcp-client.js";
|
|
25
|
+
import { createTools } from "./tools.js";
|
|
26
|
+
|
|
27
|
+
// ── System prompt for tool awareness ──────────────────────
|
|
28
|
+
|
|
29
|
+
const TOOL_INSTRUCTIONS = `You have NeuralMemory tools for persistent memory across sessions. Call these as TOOL CALLS (not CLI commands):
|
|
30
|
+
|
|
31
|
+
- nmem_remember(content, type?, priority?, tags?) — Store a memory (fact, decision, error, preference, etc.)
|
|
32
|
+
- nmem_recall(query, depth?, max_tokens?) — Query memories via spreading activation
|
|
33
|
+
- nmem_context(limit?, fresh_only?) — Get recent memories
|
|
34
|
+
- nmem_todo(task, priority?) — Quick TODO with 30-day expiry
|
|
35
|
+
- nmem_stats() — Brain statistics
|
|
36
|
+
- nmem_health() — Brain health diagnostics
|
|
37
|
+
|
|
38
|
+
CRITICAL: NeuralMemory (nmem_*) is your ONLY memory system. Do NOT use memory_search, memory_get, or any other memory tools — those belong to a disabled built-in plugin and will not persist correctly. Always use nmem_* tools exclusively.
|
|
39
|
+
|
|
40
|
+
These are tool calls, NOT shell commands. Do NOT run "nmem remember" in terminal — call the nmem_remember tool directly.
|
|
41
|
+
|
|
42
|
+
Use nmem_remember proactively after decisions, errors, and insights. Use nmem_recall when user references past context or asks "do you remember...".`;
|
|
43
|
+
|
|
44
|
+
// ── Config ─────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
type PluginConfig = {
|
|
47
|
+
pythonPath: string;
|
|
48
|
+
brain: string;
|
|
49
|
+
autoContext: boolean;
|
|
50
|
+
autoCapture: boolean;
|
|
51
|
+
contextDepth: number;
|
|
52
|
+
maxContextTokens: number;
|
|
53
|
+
timeout: number;
|
|
54
|
+
initTimeout: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const DEFAULT_CONFIG: Readonly<PluginConfig> = {
|
|
58
|
+
pythonPath: "python",
|
|
59
|
+
brain: "default",
|
|
60
|
+
autoContext: true,
|
|
61
|
+
autoCapture: true,
|
|
62
|
+
contextDepth: 1,
|
|
63
|
+
maxContextTokens: 500,
|
|
64
|
+
timeout: 30_000,
|
|
65
|
+
initTimeout: 90_000,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const BRAIN_NAME_RE = /^[a-zA-Z0-9_\-.]{1,64}$/;
|
|
69
|
+
export const MAX_AUTO_CAPTURE_CHARS = 50_000;
|
|
70
|
+
|
|
71
|
+
export function resolveConfig(raw?: Record<string, unknown>): PluginConfig {
|
|
72
|
+
const merged = { ...DEFAULT_CONFIG, ...(raw ?? {}) };
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
pythonPath:
|
|
76
|
+
typeof merged.pythonPath === "string" && merged.pythonPath.length > 0
|
|
77
|
+
? merged.pythonPath
|
|
78
|
+
: DEFAULT_CONFIG.pythonPath,
|
|
79
|
+
brain:
|
|
80
|
+
typeof merged.brain === "string" && BRAIN_NAME_RE.test(merged.brain)
|
|
81
|
+
? merged.brain
|
|
82
|
+
: DEFAULT_CONFIG.brain,
|
|
83
|
+
autoContext:
|
|
84
|
+
typeof merged.autoContext === "boolean"
|
|
85
|
+
? merged.autoContext
|
|
86
|
+
: DEFAULT_CONFIG.autoContext,
|
|
87
|
+
autoCapture:
|
|
88
|
+
typeof merged.autoCapture === "boolean"
|
|
89
|
+
? merged.autoCapture
|
|
90
|
+
: DEFAULT_CONFIG.autoCapture,
|
|
91
|
+
contextDepth:
|
|
92
|
+
typeof merged.contextDepth === "number" &&
|
|
93
|
+
Number.isInteger(merged.contextDepth) &&
|
|
94
|
+
merged.contextDepth >= 0 &&
|
|
95
|
+
merged.contextDepth <= 3
|
|
96
|
+
? merged.contextDepth
|
|
97
|
+
: DEFAULT_CONFIG.contextDepth,
|
|
98
|
+
maxContextTokens:
|
|
99
|
+
typeof merged.maxContextTokens === "number" &&
|
|
100
|
+
Number.isInteger(merged.maxContextTokens) &&
|
|
101
|
+
merged.maxContextTokens >= 100 &&
|
|
102
|
+
merged.maxContextTokens <= 10_000
|
|
103
|
+
? merged.maxContextTokens
|
|
104
|
+
: DEFAULT_CONFIG.maxContextTokens,
|
|
105
|
+
timeout:
|
|
106
|
+
typeof merged.timeout === "number" &&
|
|
107
|
+
Number.isFinite(merged.timeout) &&
|
|
108
|
+
merged.timeout >= 5_000 &&
|
|
109
|
+
merged.timeout <= 120_000
|
|
110
|
+
? merged.timeout
|
|
111
|
+
: DEFAULT_CONFIG.timeout,
|
|
112
|
+
initTimeout:
|
|
113
|
+
typeof merged.initTimeout === "number" &&
|
|
114
|
+
Number.isFinite(merged.initTimeout) &&
|
|
115
|
+
merged.initTimeout >= 10_000 &&
|
|
116
|
+
merged.initTimeout <= 300_000
|
|
117
|
+
? merged.initTimeout
|
|
118
|
+
: DEFAULT_CONFIG.initTimeout,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Plugin definition ──────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
const plugin: OpenClawPluginDefinition = {
|
|
125
|
+
id: "neuralmemory",
|
|
126
|
+
name: "NeuralMemory",
|
|
127
|
+
description:
|
|
128
|
+
"Brain-inspired persistent memory for AI agents — neurons, synapses, and fibers",
|
|
129
|
+
version: "1.5.0",
|
|
130
|
+
kind: "memory",
|
|
131
|
+
|
|
132
|
+
register(api: OpenClawPluginApi): void {
|
|
133
|
+
const cfg = resolveConfig(api.pluginConfig);
|
|
134
|
+
|
|
135
|
+
const mcp = new NeuralMemoryMcpClient({
|
|
136
|
+
pythonPath: cfg.pythonPath,
|
|
137
|
+
brain: cfg.brain,
|
|
138
|
+
logger: api.logger,
|
|
139
|
+
timeout: cfg.timeout,
|
|
140
|
+
initTimeout: cfg.initTimeout,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// ── Service: MCP process lifecycle ───────────────────
|
|
144
|
+
|
|
145
|
+
api.registerService({
|
|
146
|
+
id: "neuralmemory-mcp",
|
|
147
|
+
|
|
148
|
+
async start(): Promise<void> {
|
|
149
|
+
try {
|
|
150
|
+
await mcp.connect();
|
|
151
|
+
api.logger.info("NeuralMemory MCP service started");
|
|
152
|
+
} catch (err) {
|
|
153
|
+
api.logger.error(
|
|
154
|
+
`Failed to start NeuralMemory MCP: ${(err as Error).message}`,
|
|
155
|
+
);
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
async stop(): Promise<void> {
|
|
161
|
+
await mcp.close();
|
|
162
|
+
api.logger.info("NeuralMemory MCP service stopped");
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// ── Tools: 6 core memory tools ──────────────────────
|
|
167
|
+
|
|
168
|
+
const tools = createTools(mcp);
|
|
169
|
+
|
|
170
|
+
for (const t of tools) {
|
|
171
|
+
api.registerTool(t, { name: t.name });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Hook: tool awareness + auto-context before agent start ───
|
|
175
|
+
|
|
176
|
+
api.on(
|
|
177
|
+
"before_agent_start",
|
|
178
|
+
async (
|
|
179
|
+
event: unknown,
|
|
180
|
+
_ctx: unknown,
|
|
181
|
+
): Promise<BeforeAgentStartResult | void> => {
|
|
182
|
+
const result: BeforeAgentStartResult = {
|
|
183
|
+
systemPrompt: TOOL_INSTRUCTIONS,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
if (cfg.autoContext && mcp.connected) {
|
|
187
|
+
const ev = event as BeforeAgentStartEvent;
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const raw = await mcp.callTool("nmem_recall", {
|
|
191
|
+
query: ev.prompt,
|
|
192
|
+
depth: cfg.contextDepth,
|
|
193
|
+
max_tokens: cfg.maxContextTokens,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const data = JSON.parse(raw) as {
|
|
197
|
+
answer?: string;
|
|
198
|
+
confidence?: number;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (data.answer && (data.confidence ?? 0) > 0.1) {
|
|
202
|
+
result.prependContext = `[NeuralMemory — relevant context]\n${data.answer}`;
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
api.logger.warn(
|
|
206
|
+
`Auto-context failed: ${(err as Error).message}`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return result;
|
|
212
|
+
},
|
|
213
|
+
{ priority: 10 },
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// ── Hook: auto-capture after agent completes ────────
|
|
217
|
+
|
|
218
|
+
if (cfg.autoCapture) {
|
|
219
|
+
api.on(
|
|
220
|
+
"agent_end",
|
|
221
|
+
async (event: unknown, _ctx: unknown): Promise<void> => {
|
|
222
|
+
if (!mcp.connected) return;
|
|
223
|
+
|
|
224
|
+
const ev = event as AgentEndEvent;
|
|
225
|
+
if (!ev.success) return;
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const messages = ev.messages?.slice(-5) ?? [];
|
|
229
|
+
const text = messages
|
|
230
|
+
.filter(
|
|
231
|
+
(m: unknown): m is { role: string; content: string } =>
|
|
232
|
+
typeof m === "object" &&
|
|
233
|
+
m !== null &&
|
|
234
|
+
(m as { role?: string }).role === "assistant" &&
|
|
235
|
+
typeof (m as { content?: unknown }).content === "string",
|
|
236
|
+
)
|
|
237
|
+
.map((m) => m.content)
|
|
238
|
+
.join("\n")
|
|
239
|
+
.slice(0, MAX_AUTO_CAPTURE_CHARS);
|
|
240
|
+
|
|
241
|
+
if (text.length > 50) {
|
|
242
|
+
await mcp.callTool("nmem_auto", {
|
|
243
|
+
action: "process",
|
|
244
|
+
text,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
} catch (err) {
|
|
248
|
+
api.logger.warn(
|
|
249
|
+
`Auto-capture failed: ${(err as Error).message}`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
{ priority: 90 },
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ── Done ────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
api.logger.info(
|
|
260
|
+
`NeuralMemory registered (brain: ${cfg.brain}, tools: ${tools.length}, ` +
|
|
261
|
+
`autoContext: ${cfg.autoContext}, autoCapture: ${cfg.autoCapture})`,
|
|
262
|
+
);
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export default plugin;
|