@memorystack/clawdbot-memorystack 1.0.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/README.md +102 -0
- package/clawdbot.plugin.json +66 -0
- package/config.ts +32 -0
- package/hooks/capture.ts +96 -0
- package/hooks/recall.ts +104 -0
- package/index.ts +54 -0
- package/logger.ts +36 -0
- package/package.json +39 -0
- package/tools/add.ts +58 -0
- package/tools/search.ts +107 -0
- package/tools/stats.ts +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# MemoryStack Plugin for Clawdbot
|
|
2
|
+
|
|
3
|
+
Long-term memory for Clawdbot. Automatically remembers conversations, recalls relevant context, and builds a persistent memory layer — all powered by [MemoryStack](https://memorystack.app) cloud.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd extensions/memorystack
|
|
9
|
+
npm install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Or install from npm (once published):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
clawdbot plugins install @memorystack/clawdbot-memorystack
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Restart Clawdbot after installing.
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
The only required value is your MemoryStack API key. Get one at [memorystack.app/dashboard/api-keys](https://memorystack.app/dashboard/api-keys).
|
|
23
|
+
|
|
24
|
+
Set it as an environment variable:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export MEMORYSTACK_API_KEY="ms_proj_..."
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or configure it directly in `clawdbot.json`:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"plugins": {
|
|
35
|
+
"slots": {
|
|
36
|
+
"memory": "clawdbot-memorystack"
|
|
37
|
+
},
|
|
38
|
+
"entries": {
|
|
39
|
+
"clawdbot-memorystack": {
|
|
40
|
+
"enabled": true,
|
|
41
|
+
"config": {
|
|
42
|
+
"apiKey": "${MEMORYSTACK_API_KEY}",
|
|
43
|
+
"autoRecall": true,
|
|
44
|
+
"autoCapture": true,
|
|
45
|
+
"maxRecallResults": 5
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Advanced options
|
|
54
|
+
|
|
55
|
+
| Key | Type | Default | Description |
|
|
56
|
+
|-----|------|---------|-------------|
|
|
57
|
+
| `apiKey` | `string` | - | Your MemoryStack API key (required) |
|
|
58
|
+
| `baseUrl` | `string` | `https://memorystack.app` | API endpoint URL |
|
|
59
|
+
| `autoRecall` | `boolean` | `true` | Inject relevant memories before every AI turn |
|
|
60
|
+
| `autoCapture` | `boolean` | `true` | Automatically store conversation content after every turn |
|
|
61
|
+
| `maxRecallResults` | `number` | `5` | Max memories injected into context per turn (1-20) |
|
|
62
|
+
| `debug` | `boolean` | `false` | Verbose debug logs for API calls and responses |
|
|
63
|
+
|
|
64
|
+
## How it works
|
|
65
|
+
|
|
66
|
+
Once installed, the plugin works automatically with zero interaction:
|
|
67
|
+
|
|
68
|
+
- **Auto-Recall** — Before every AI turn, the plugin queries MemoryStack for relevant memories and injects them as context using semantic search.
|
|
69
|
+
- **Auto-Capture** — After every AI turn, the last user/assistant exchange is sent to MemoryStack for extraction and long-term storage.
|
|
70
|
+
|
|
71
|
+
Everything runs in the cloud. MemoryStack handles importance scoring, contradiction detection, consolidation, and reflection on its end.
|
|
72
|
+
|
|
73
|
+
## AI Tools
|
|
74
|
+
|
|
75
|
+
The AI can use these tools autonomously during conversations:
|
|
76
|
+
|
|
77
|
+
| Tool | Description |
|
|
78
|
+
|------|-------------|
|
|
79
|
+
| `memorystack_search` | Search memories by query with semantic search |
|
|
80
|
+
| `memorystack_add` | Save information to long-term memory |
|
|
81
|
+
| `memorystack_stats` | View memory usage statistics |
|
|
82
|
+
|
|
83
|
+
## Features
|
|
84
|
+
|
|
85
|
+
- **Hybrid Search** - Vector similarity + text search with RRF ranking
|
|
86
|
+
- **Importance Scoring** - Automatic ranking of what matters
|
|
87
|
+
- **Contradiction Detection** - Resolves conflicts and updates beliefs
|
|
88
|
+
- **Memory Consolidation** - Deduplication and merging (runs server-side)
|
|
89
|
+
- **Temporal Decay** - Simulates natural forgetting
|
|
90
|
+
- **Reflection** - Discovers patterns and generates insights
|
|
91
|
+
|
|
92
|
+
## Links
|
|
93
|
+
|
|
94
|
+
- 🌐 **Website**: https://memorystack.app
|
|
95
|
+
- 📚 **Documentation**: https://memorystack.app/docs
|
|
96
|
+
- 🚀 **Quick Start**: https://memorystack.app/docs/quickstart
|
|
97
|
+
- 🔑 **Get API Key**: https://memorystack.app/dashboard/api-keys
|
|
98
|
+
- 💰 **Pricing**: https://memorystack.app/pricing
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "clawdbot-memorystack",
|
|
3
|
+
"kind": "memory",
|
|
4
|
+
"uiHints": {
|
|
5
|
+
"apiKey": {
|
|
6
|
+
"label": "MemoryStack API Key",
|
|
7
|
+
"sensitive": true,
|
|
8
|
+
"placeholder": "ms_proj_...",
|
|
9
|
+
"help": "Your API key from memorystack.app/dashboard/api-keys (or use ${MEMORYSTACK_API_KEY})"
|
|
10
|
+
},
|
|
11
|
+
"baseUrl": {
|
|
12
|
+
"label": "API Base URL",
|
|
13
|
+
"placeholder": "https://memorystack.app",
|
|
14
|
+
"help": "MemoryStack API endpoint (default: https://memorystack.app)",
|
|
15
|
+
"advanced": true
|
|
16
|
+
},
|
|
17
|
+
"autoRecall": {
|
|
18
|
+
"label": "Auto-Recall",
|
|
19
|
+
"help": "Inject relevant memories before every AI turn"
|
|
20
|
+
},
|
|
21
|
+
"autoCapture": {
|
|
22
|
+
"label": "Auto-Capture",
|
|
23
|
+
"help": "Automatically store important information from conversations"
|
|
24
|
+
},
|
|
25
|
+
"maxRecallResults": {
|
|
26
|
+
"label": "Max Recall Results",
|
|
27
|
+
"placeholder": "5",
|
|
28
|
+
"help": "Maximum memories injected into context per turn",
|
|
29
|
+
"advanced": true
|
|
30
|
+
},
|
|
31
|
+
"debug": {
|
|
32
|
+
"label": "Debug Logging",
|
|
33
|
+
"help": "Enable verbose debug logs for API calls and responses",
|
|
34
|
+
"advanced": true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"configSchema": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"additionalProperties": false,
|
|
40
|
+
"properties": {
|
|
41
|
+
"apiKey": {
|
|
42
|
+
"type": "string"
|
|
43
|
+
},
|
|
44
|
+
"baseUrl": {
|
|
45
|
+
"type": "string"
|
|
46
|
+
},
|
|
47
|
+
"autoRecall": {
|
|
48
|
+
"type": "boolean"
|
|
49
|
+
},
|
|
50
|
+
"autoCapture": {
|
|
51
|
+
"type": "boolean"
|
|
52
|
+
},
|
|
53
|
+
"maxRecallResults": {
|
|
54
|
+
"type": "number",
|
|
55
|
+
"minimum": 1,
|
|
56
|
+
"maximum": 20
|
|
57
|
+
},
|
|
58
|
+
"debug": {
|
|
59
|
+
"type": "boolean"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"required": [
|
|
63
|
+
"apiKey"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
}
|
package/config.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
|
|
3
|
+
export const memorystackConfigSchema = Type.Object({
|
|
4
|
+
apiKey: Type.String(),
|
|
5
|
+
baseUrl: Type.Optional(Type.String()),
|
|
6
|
+
autoRecall: Type.Optional(Type.Boolean()),
|
|
7
|
+
autoCapture: Type.Optional(Type.Boolean()),
|
|
8
|
+
maxRecallResults: Type.Optional(Type.Number({ minimum: 1, maximum: 20 })),
|
|
9
|
+
debug: Type.Optional(Type.Boolean()),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type MemorystackConfig = {
|
|
13
|
+
apiKey: string;
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
autoRecall: boolean;
|
|
16
|
+
autoCapture: boolean;
|
|
17
|
+
maxRecallResults: number;
|
|
18
|
+
debug: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function parseConfig(rawConfig: unknown): MemorystackConfig {
|
|
22
|
+
const cfg = rawConfig as Partial<MemorystackConfig>;
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
apiKey: cfg.apiKey || process.env.MEMORYSTACK_API_KEY || "",
|
|
26
|
+
baseUrl: cfg.baseUrl || "https://memorystack.app",
|
|
27
|
+
autoRecall: cfg.autoRecall ?? true,
|
|
28
|
+
autoCapture: cfg.autoCapture ?? true,
|
|
29
|
+
maxRecallResults: cfg.maxRecallResults ?? 5,
|
|
30
|
+
debug: cfg.debug ?? false,
|
|
31
|
+
};
|
|
32
|
+
}
|
package/hooks/capture.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { MemoryStackClient } from "@memorystack/sdk";
|
|
2
|
+
import type { MemorystackConfig } from "../config.ts";
|
|
3
|
+
import { log } from "../logger.ts";
|
|
4
|
+
|
|
5
|
+
function getLastTurn(messages: unknown[]): unknown[] {
|
|
6
|
+
let lastUserIdx = -1;
|
|
7
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
8
|
+
const msg = messages[i];
|
|
9
|
+
if (
|
|
10
|
+
msg &&
|
|
11
|
+
typeof msg === "object" &&
|
|
12
|
+
(msg as Record<string, unknown>).role === "user"
|
|
13
|
+
) {
|
|
14
|
+
lastUserIdx = i;
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return lastUserIdx >= 0 ? messages.slice(lastUserIdx) : messages;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildCaptureHandler(cfg: MemorystackConfig) {
|
|
22
|
+
return async (event: Record<string, unknown>) => {
|
|
23
|
+
if (
|
|
24
|
+
!event.success ||
|
|
25
|
+
!Array.isArray(event.messages) ||
|
|
26
|
+
event.messages.length === 0
|
|
27
|
+
)
|
|
28
|
+
return;
|
|
29
|
+
|
|
30
|
+
const lastTurn = getLastTurn(event.messages);
|
|
31
|
+
|
|
32
|
+
const texts: string[] = [];
|
|
33
|
+
for (const msg of lastTurn) {
|
|
34
|
+
if (!msg || typeof msg !== "object") continue;
|
|
35
|
+
const msgObj = msg as Record<string, unknown>;
|
|
36
|
+
const role = msgObj.role;
|
|
37
|
+
if (role !== "user" && role !== "assistant") continue;
|
|
38
|
+
|
|
39
|
+
const content = msgObj.content;
|
|
40
|
+
|
|
41
|
+
const parts: string[] = [];
|
|
42
|
+
|
|
43
|
+
if (typeof content === "string") {
|
|
44
|
+
parts.push(content);
|
|
45
|
+
} else if (Array.isArray(content)) {
|
|
46
|
+
for (const block of content) {
|
|
47
|
+
if (!block || typeof block !== "object") continue;
|
|
48
|
+
const b = block as Record<string, unknown>;
|
|
49
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
50
|
+
parts.push(b.text);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (parts.length > 0) {
|
|
56
|
+
texts.push(`[role: ${role}]\n${parts.join("\n")}\n[${role}:end]`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Filter out injected context and short messages
|
|
61
|
+
const captured = texts
|
|
62
|
+
.map((t) =>
|
|
63
|
+
t
|
|
64
|
+
.replace(
|
|
65
|
+
/<memorystack-context>[\s\S]*?<\/memorystack-context>\s*/g,
|
|
66
|
+
"",
|
|
67
|
+
)
|
|
68
|
+
.trim(),
|
|
69
|
+
)
|
|
70
|
+
.filter((t) => t.length >= 10 && t.length <= 500);
|
|
71
|
+
|
|
72
|
+
if (captured.length === 0) return;
|
|
73
|
+
|
|
74
|
+
const content = captured.join("\n\n");
|
|
75
|
+
|
|
76
|
+
log.debug(
|
|
77
|
+
`capturing ${captured.length} texts (${content.length} chars)`,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const client = new MemoryStackClient({
|
|
82
|
+
apiKey: cfg.apiKey,
|
|
83
|
+
baseUrl: cfg.baseUrl,
|
|
84
|
+
enableLogging: cfg.debug,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await client.add(content, {
|
|
88
|
+
metadata: { source: "clawdbot_auto_capture", timestamp: new Date().toISOString() },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
log.debug(`captured ${captured.length} messages successfully`);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
log.error("capture failed", err);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
package/hooks/recall.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { MemoryStackClient } from "@memorystack/sdk";
|
|
2
|
+
import type { MemorystackConfig } from "../config.ts";
|
|
3
|
+
import { log } from "../logger.ts";
|
|
4
|
+
|
|
5
|
+
const MAX_CONTEXT_CHARS = 2000;
|
|
6
|
+
|
|
7
|
+
interface MemoryResult {
|
|
8
|
+
content: string;
|
|
9
|
+
memory_type?: string;
|
|
10
|
+
confidence?: number;
|
|
11
|
+
created_at?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function formatGroupedMemories(memories: MemoryResult[]): string {
|
|
15
|
+
// Group by memory type
|
|
16
|
+
const groups: Record<string, MemoryResult[]> = {};
|
|
17
|
+
const ungrouped: MemoryResult[] = [];
|
|
18
|
+
|
|
19
|
+
for (const m of memories) {
|
|
20
|
+
const type = m.memory_type || "other";
|
|
21
|
+
if (type === "other") {
|
|
22
|
+
ungrouped.push(m);
|
|
23
|
+
} else {
|
|
24
|
+
groups[type] = groups[type] || [];
|
|
25
|
+
groups[type].push(m);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const sections: string[] = [];
|
|
30
|
+
const typeLabels: Record<string, string> = {
|
|
31
|
+
fact: "📋 Facts",
|
|
32
|
+
preference: "💜 Preferences",
|
|
33
|
+
episode: "📅 Recent Context",
|
|
34
|
+
procedure: "🔧 Procedures",
|
|
35
|
+
belief: "💭 Beliefs",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Format grouped memories
|
|
39
|
+
for (const [type, items] of Object.entries(groups)) {
|
|
40
|
+
const label = typeLabels[type] || type.charAt(0).toUpperCase() + type.slice(1);
|
|
41
|
+
const lines = items.map((m) => {
|
|
42
|
+
const conf = m.confidence && m.confidence >= 0.8 ? ` (${Math.round(m.confidence * 100)}%)` : "";
|
|
43
|
+
return `- ${m.content}${conf}`;
|
|
44
|
+
});
|
|
45
|
+
sections.push(`## ${label}\n${lines.join("\n")}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Add ungrouped at end
|
|
49
|
+
if (ungrouped.length > 0) {
|
|
50
|
+
const lines = ungrouped.map((m) => `- ${m.content}`);
|
|
51
|
+
sections.push(`## Other\n${lines.join("\n")}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return sections.join("\n\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function buildRecallHandler(cfg: MemorystackConfig) {
|
|
58
|
+
return async (event: Record<string, unknown>) => {
|
|
59
|
+
const prompt = event.prompt as string | undefined;
|
|
60
|
+
if (!prompt || prompt.length < 10) return;
|
|
61
|
+
|
|
62
|
+
log.debug(`recalling for prompt (${prompt.length} chars)`);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const client = new MemoryStackClient({
|
|
66
|
+
apiKey: cfg.apiKey,
|
|
67
|
+
baseUrl: cfg.baseUrl,
|
|
68
|
+
enableLogging: cfg.debug,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const results = await client.search(prompt, {
|
|
72
|
+
limit: cfg.maxRecallResults,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (results.count === 0) {
|
|
76
|
+
log.debug("no memories to inject");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const formattedMemories = formatGroupedMemories(results.results);
|
|
81
|
+
|
|
82
|
+
// Truncate if too long
|
|
83
|
+
const truncated = formattedMemories.length > MAX_CONTEXT_CHARS
|
|
84
|
+
? formattedMemories.slice(0, MAX_CONTEXT_CHARS) + "\n...(truncated)"
|
|
85
|
+
: formattedMemories;
|
|
86
|
+
|
|
87
|
+
const context = `<memorystack-context>
|
|
88
|
+
The following is recalled context about the user. Reference it only when relevant.
|
|
89
|
+
|
|
90
|
+
${truncated}
|
|
91
|
+
|
|
92
|
+
Use these memories naturally when relevant — including indirect connections — but don't force them into every response or make assumptions beyond what's stated.
|
|
93
|
+
</memorystack-context>`;
|
|
94
|
+
|
|
95
|
+
log.debug(`injecting ${results.count} memories (${context.length} chars)`);
|
|
96
|
+
|
|
97
|
+
return { prependContext: context };
|
|
98
|
+
} catch (err) {
|
|
99
|
+
log.error("recall failed", err);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
package/index.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
2
|
+
import { parseConfig, memorystackConfigSchema } from "./config.ts";
|
|
3
|
+
import { initLogger } from "./logger.ts";
|
|
4
|
+
import { registerSearchTool } from "./tools/search.ts";
|
|
5
|
+
import { registerAddTool } from "./tools/add.ts";
|
|
6
|
+
import { registerStatsTool } from "./tools/stats.ts";
|
|
7
|
+
import { buildRecallHandler } from "./hooks/recall.ts";
|
|
8
|
+
import { buildCaptureHandler } from "./hooks/capture.ts";
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
id: "clawdbot-memorystack",
|
|
12
|
+
name: "MemoryStack",
|
|
13
|
+
description: "Clawdbot powered by MemoryStack plugin",
|
|
14
|
+
kind: "memory" as const,
|
|
15
|
+
configSchema: memorystackConfigSchema,
|
|
16
|
+
|
|
17
|
+
register(api: ClawdbotPluginApi) {
|
|
18
|
+
const cfg = parseConfig(api.pluginConfig);
|
|
19
|
+
|
|
20
|
+
if (!cfg.apiKey) {
|
|
21
|
+
api.logger.error("memorystack: API key is required. Set MEMORYSTACK_API_KEY or configure in plugin config.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
initLogger(api.logger, cfg.debug);
|
|
26
|
+
|
|
27
|
+
// Register tools
|
|
28
|
+
registerSearchTool(api, cfg);
|
|
29
|
+
registerAddTool(api, cfg);
|
|
30
|
+
registerStatsTool(api, cfg);
|
|
31
|
+
|
|
32
|
+
// Auto-recall hook
|
|
33
|
+
if (cfg.autoRecall) {
|
|
34
|
+
const recallHandler = buildRecallHandler(cfg);
|
|
35
|
+
api.on("before_agent_start", recallHandler);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Auto-capture hook
|
|
39
|
+
if (cfg.autoCapture) {
|
|
40
|
+
api.on("agent_end", buildCaptureHandler(cfg));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Register service
|
|
44
|
+
api.registerService({
|
|
45
|
+
id: "clawdbot-memorystack",
|
|
46
|
+
start: () => {
|
|
47
|
+
api.logger.info("memorystack: connected");
|
|
48
|
+
},
|
|
49
|
+
stop: () => {
|
|
50
|
+
api.logger.info("memorystack: stopped");
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
};
|
package/logger.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Logger } from "clawdbot/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
let logger: Logger;
|
|
4
|
+
let debugEnabled = false;
|
|
5
|
+
|
|
6
|
+
export function initLogger(l: Logger, debug: boolean): void {
|
|
7
|
+
logger = l;
|
|
8
|
+
debugEnabled = debug;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const log = {
|
|
12
|
+
info: (msg: string, ...args: unknown[]) => {
|
|
13
|
+
logger?.info(`memorystack: ${msg}`, ...args);
|
|
14
|
+
},
|
|
15
|
+
warn: (msg: string, ...args: unknown[]) => {
|
|
16
|
+
logger?.warn(`memorystack: ${msg}`, ...args);
|
|
17
|
+
},
|
|
18
|
+
error: (msg: string, err?: unknown) => {
|
|
19
|
+
logger?.error(`memorystack: ${msg}`, err);
|
|
20
|
+
},
|
|
21
|
+
debug: (msg: string, data?: unknown) => {
|
|
22
|
+
if (debugEnabled) {
|
|
23
|
+
logger?.debug?.(`memorystack: ${msg}`, data);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
debugRequest: (method: string, params: unknown) => {
|
|
27
|
+
if (debugEnabled) {
|
|
28
|
+
logger?.debug?.(`memorystack → ${method}`, params);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
debugResponse: (method: string, result: unknown) => {
|
|
32
|
+
if (debugEnabled) {
|
|
33
|
+
logger?.debug?.(`memorystack ← ${method}`, result);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memorystack/clawdbot-memorystack",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Clawdbot MemoryStack memory plugin - long-term memory for your AI assistant",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "MemoryStack <support@memorystack.app>",
|
|
8
|
+
"homepage": "https://memorystack.app",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/memorystack-labs/memorystack"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"clawdbot",
|
|
15
|
+
"moltbot",
|
|
16
|
+
"memory",
|
|
17
|
+
"memorystack",
|
|
18
|
+
"ai",
|
|
19
|
+
"long-term-memory"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@memorystack/sdk": "^1.0.3",
|
|
26
|
+
"@sinclair/typebox": "0.34.47"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"clawdbot": ">=2026.1.24"
|
|
30
|
+
},
|
|
31
|
+
"clawdbot": {
|
|
32
|
+
"extensions": [
|
|
33
|
+
"./index.ts"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"typescript": "^5.9.3"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/tools/add.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
3
|
+
import { MemoryStackClient } from "@memorystack/sdk";
|
|
4
|
+
import type { MemorystackConfig } from "../config.ts";
|
|
5
|
+
import { log } from "../logger.ts";
|
|
6
|
+
|
|
7
|
+
export function registerAddTool(
|
|
8
|
+
api: ClawdbotPluginApi,
|
|
9
|
+
cfg: MemorystackConfig,
|
|
10
|
+
): void {
|
|
11
|
+
api.registerTool(
|
|
12
|
+
{
|
|
13
|
+
name: "memorystack_add",
|
|
14
|
+
label: "Memory Add",
|
|
15
|
+
description: "Save important information to long-term memory with automatic importance scoring.",
|
|
16
|
+
parameters: Type.Object({
|
|
17
|
+
text: Type.String({ description: "Information to remember" }),
|
|
18
|
+
userId: Type.Optional(Type.String({ description: "User ID (for B2B apps)" })),
|
|
19
|
+
}),
|
|
20
|
+
async execute(
|
|
21
|
+
_toolCallId: string,
|
|
22
|
+
params: { text: string; userId?: string },
|
|
23
|
+
) {
|
|
24
|
+
log.debugRequest("add", { textLength: params.text.length, userId: params.userId });
|
|
25
|
+
|
|
26
|
+
const client = new MemoryStackClient({
|
|
27
|
+
apiKey: cfg.apiKey,
|
|
28
|
+
baseUrl: cfg.baseUrl,
|
|
29
|
+
enableLogging: cfg.debug,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const result = await client.add(params.text, {
|
|
33
|
+
userId: params.userId,
|
|
34
|
+
metadata: { source: "clawdbot_tool" },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
log.debugResponse("add", {
|
|
38
|
+
memories_created: result.memories_created,
|
|
39
|
+
memory_ids: result.memory_ids,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const preview =
|
|
43
|
+
params.text.length > 80 ? `${params.text.slice(0, 80)}…` : params.text;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text" as const,
|
|
49
|
+
text: `Stored: "${preview}"\nCreated ${result.memories_created} memory (IDs: ${result.memory_ids.join(", ")})`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
details: result,
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{ name: "memorystack_add" },
|
|
57
|
+
);
|
|
58
|
+
}
|
package/tools/search.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
3
|
+
import { MemoryStackClient } from "@memorystack/sdk";
|
|
4
|
+
import type { MemorystackConfig } from "../config.ts";
|
|
5
|
+
import { log } from "../logger.ts";
|
|
6
|
+
|
|
7
|
+
export function registerSearchTool(
|
|
8
|
+
api: ClawdbotPluginApi,
|
|
9
|
+
cfg: MemorystackConfig,
|
|
10
|
+
): void {
|
|
11
|
+
api.registerTool(
|
|
12
|
+
{
|
|
13
|
+
name: "memorystack_search",
|
|
14
|
+
label: "Memory Search",
|
|
15
|
+
description:
|
|
16
|
+
"Search through long-term memories for relevant information using semantic search. Supports filtering by memory type, confidence, and recency.",
|
|
17
|
+
parameters: Type.Object({
|
|
18
|
+
query: Type.String({ description: "Search query" }),
|
|
19
|
+
limit: Type.Optional(
|
|
20
|
+
Type.Number({ description: "Max results (default: 5)" }),
|
|
21
|
+
),
|
|
22
|
+
memory_type: Type.Optional(
|
|
23
|
+
Type.String({
|
|
24
|
+
description:
|
|
25
|
+
"Filter by type: fact, preference, episode, procedure, belief",
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
28
|
+
min_confidence: Type.Optional(
|
|
29
|
+
Type.Number({
|
|
30
|
+
description: "Minimum confidence score 0-1 (default: 0)",
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
days_ago: Type.Optional(
|
|
34
|
+
Type.Number({
|
|
35
|
+
description: "Only memories from last N days",
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
}),
|
|
39
|
+
async execute(
|
|
40
|
+
_toolCallId: string,
|
|
41
|
+
params: {
|
|
42
|
+
query: string;
|
|
43
|
+
limit?: number;
|
|
44
|
+
memory_type?: string;
|
|
45
|
+
min_confidence?: number;
|
|
46
|
+
days_ago?: number;
|
|
47
|
+
},
|
|
48
|
+
) {
|
|
49
|
+
const limit = params.limit ?? 5;
|
|
50
|
+
log.debugRequest("search", { query: params.query, limit, ...params });
|
|
51
|
+
|
|
52
|
+
const client = new MemoryStackClient({
|
|
53
|
+
apiKey: cfg.apiKey,
|
|
54
|
+
baseUrl: cfg.baseUrl,
|
|
55
|
+
enableLogging: cfg.debug,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const searchOpts: Record<string, unknown> = { limit };
|
|
59
|
+
if (params.memory_type) {
|
|
60
|
+
searchOpts.memory_type = params.memory_type;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const results = await client.search(params.query, searchOpts);
|
|
64
|
+
|
|
65
|
+
if (results.count === 0) {
|
|
66
|
+
return {
|
|
67
|
+
content: [
|
|
68
|
+
{ type: "text" as const, text: "No relevant memories found." },
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const text = results.results
|
|
74
|
+
.map((r, i) => {
|
|
75
|
+
const type = r.memory_type ? ` [${r.memory_type}]` : "";
|
|
76
|
+
const conf = r.confidence
|
|
77
|
+
? ` (${(r.confidence * 100).toFixed(0)}%)`
|
|
78
|
+
: "";
|
|
79
|
+
return `${i + 1}. ${r.content}${type}${conf}`;
|
|
80
|
+
})
|
|
81
|
+
.join("\n");
|
|
82
|
+
|
|
83
|
+
log.debugResponse("search", { count: results.count });
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text" as const,
|
|
89
|
+
text: `Found ${results.count} memories:\n\n${text}`,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
details: {
|
|
93
|
+
count: results.count,
|
|
94
|
+
mode: results.mode,
|
|
95
|
+
memories: results.results.map((r) => ({
|
|
96
|
+
id: r.id,
|
|
97
|
+
content: r.content,
|
|
98
|
+
memory_type: r.memory_type,
|
|
99
|
+
confidence: r.confidence,
|
|
100
|
+
})),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{ name: "memorystack_search" },
|
|
106
|
+
);
|
|
107
|
+
}
|
package/tools/stats.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
3
|
+
import { MemoryStackClient } from "@memorystack/sdk";
|
|
4
|
+
import type { MemorystackConfig } from "../config.ts";
|
|
5
|
+
import { log } from "../logger.ts";
|
|
6
|
+
|
|
7
|
+
export function registerStatsTool(
|
|
8
|
+
api: ClawdbotPluginApi,
|
|
9
|
+
cfg: MemorystackConfig,
|
|
10
|
+
): void {
|
|
11
|
+
api.registerTool(
|
|
12
|
+
{
|
|
13
|
+
name: "memorystack_stats",
|
|
14
|
+
label: "Memory Stats",
|
|
15
|
+
description: "Get statistics about memory usage and API calls.",
|
|
16
|
+
parameters: Type.Object({}),
|
|
17
|
+
async execute() {
|
|
18
|
+
log.debugRequest("stats", {});
|
|
19
|
+
|
|
20
|
+
const client = new MemoryStackClient({
|
|
21
|
+
apiKey: cfg.apiKey,
|
|
22
|
+
baseUrl: cfg.baseUrl,
|
|
23
|
+
enableLogging: cfg.debug,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const stats = await client.getStats();
|
|
27
|
+
|
|
28
|
+
log.debugResponse("stats", {
|
|
29
|
+
total_memories: stats.totals.total_memories,
|
|
30
|
+
api_calls: stats.usage.current_month_api_calls,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const text = [
|
|
34
|
+
`**Total Memories:** ${stats.totals.total_memories}`,
|
|
35
|
+
`**API Calls:** ${stats.usage.current_month_api_calls} / ${stats.usage.monthly_api_limit}`,
|
|
36
|
+
`**Plan:** ${stats.plan_tier || "Free"}`,
|
|
37
|
+
`**Storage:** ${(stats.storage.total_storage_bytes / 1024 / 1024).toFixed(2)} MB`,
|
|
38
|
+
].join("\n");
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: "text" as const,
|
|
44
|
+
text: `# MemoryStack Stats\n\n${text}`,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
details: stats,
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{ name: "memorystack_stats" },
|
|
52
|
+
);
|
|
53
|
+
}
|