@bsbofmusic/memos-memu-local-memory-tools-for-agent 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +25 -0
- package/src/tools/memos_memuk.js +116 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsbofmusic/memos-memu-local-memory-tools-for-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "MCP server — one-shot install + query for memos (PostgreSQL) and memuK (SQLite) local memory. Designed for OpenClaw agents.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { install_memory_system } from './tools/install.js';
|
|
10
10
|
import { memos_query } from './tools/memos.js';
|
|
11
11
|
import { memuk_search } from './tools/memuk.js';
|
|
12
|
+
import { memos_memuk_query } from './tools/memos_memuk.js';
|
|
12
13
|
import { verify_memory_system } from './tools/verify.js';
|
|
13
14
|
|
|
14
15
|
const SERVER_NAME = 'memos-memu-local-memory-tools-for-agent';
|
|
@@ -83,6 +84,28 @@ export const tools = [
|
|
|
83
84
|
required: ['query'],
|
|
84
85
|
},
|
|
85
86
|
},
|
|
87
|
+
{
|
|
88
|
+
name: 'memos_memuk_query',
|
|
89
|
+
description:
|
|
90
|
+
'Query BOTH memos PostgreSQL AND memuK SQLite at the SAME TIME, combine and deduplicate results. ' +
|
|
91
|
+
'Use for: fast parallel recall of both raw quotes AND topic summaries. ' +
|
|
92
|
+
'Returns combined results sorted by time (newest first), with source tagged [memos]/[memuK].',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
query: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
description: 'Keywords to search in both memo content AND memuK summaries',
|
|
99
|
+
},
|
|
100
|
+
limit: {
|
|
101
|
+
type: 'number',
|
|
102
|
+
description: 'Max results to return PER SOURCE (default 10, max 20 each)',
|
|
103
|
+
default: 10,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
required: ['query'],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
86
109
|
];
|
|
87
110
|
|
|
88
111
|
const server = new Server(
|
|
@@ -107,6 +130,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
107
130
|
return await memos_query(args);
|
|
108
131
|
case 'memuk_search':
|
|
109
132
|
return await memuk_search(args);
|
|
133
|
+
case 'memos_memuk_query':
|
|
134
|
+
return await memos_memuk_query(args);
|
|
110
135
|
default:
|
|
111
136
|
return {
|
|
112
137
|
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { detectEnv, psql, parsePsqlJson, sqlite, parseSqlite } from '../shared/db.js';
|
|
2
|
+
|
|
3
|
+
export async function memos_memuk_query({ query, limit = 10 }) {
|
|
4
|
+
if (!query || typeof query !== 'string') {
|
|
5
|
+
return {
|
|
6
|
+
content: [{ type: 'text', text: '❌ memos_memuk_query requires a non-empty "query" string.' }],
|
|
7
|
+
isError: true,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const safe = s => s.replace(/'/g, "''");
|
|
12
|
+
const lim = Math.min(parseInt(limit) || 10, 20);
|
|
13
|
+
|
|
14
|
+
// 同时调两个查询,并行执行
|
|
15
|
+
const [memosResult, memuKResult] = await Promise.all([
|
|
16
|
+
(async () => {
|
|
17
|
+
const sql = `
|
|
18
|
+
SELECT jsonb_agg(q ORDER BY created_ts DESC)
|
|
19
|
+
FROM (
|
|
20
|
+
SELECT
|
|
21
|
+
id,
|
|
22
|
+
substr(content, 1, 500) AS content,
|
|
23
|
+
created_ts,
|
|
24
|
+
visibility
|
|
25
|
+
FROM memo
|
|
26
|
+
WHERE content ILIKE '%${safe(query)}%'
|
|
27
|
+
ORDER BY created_ts DESC
|
|
28
|
+
LIMIT ${lim}
|
|
29
|
+
) q;`.trim();
|
|
30
|
+
try {
|
|
31
|
+
const raw = psql(sql);
|
|
32
|
+
const rows = parsePsqlJson(raw);
|
|
33
|
+
return { rows: Array.isArray(rows) ? rows.slice(0, lim) : [] };
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return { rows: [], error: err.message };
|
|
36
|
+
}
|
|
37
|
+
})(),
|
|
38
|
+
(async () => {
|
|
39
|
+
const sql = `
|
|
40
|
+
SELECT json_group_array(json_object(
|
|
41
|
+
'id', id,
|
|
42
|
+
'summary', substr(summary, 1, 500),
|
|
43
|
+
'memory_type', memory_type,
|
|
44
|
+
'happened_at', happened_at
|
|
45
|
+
))
|
|
46
|
+
FROM (
|
|
47
|
+
SELECT id, summary, memory_type, happened_at
|
|
48
|
+
FROM memu_memory_items
|
|
49
|
+
WHERE summary LIKE '%${safe(query)}%'
|
|
50
|
+
ORDER BY happened_at DESC
|
|
51
|
+
LIMIT ${lim}
|
|
52
|
+
) q;`.trim();
|
|
53
|
+
try {
|
|
54
|
+
const raw = sqlite(sql);
|
|
55
|
+
const rows = parseSqlite(raw);
|
|
56
|
+
return { rows: Array.isArray(rows) ? rows.slice(0, lim) : [] };
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return { rows: [], error: err.message };
|
|
59
|
+
}
|
|
60
|
+
})()
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
// 合并结果,去重
|
|
64
|
+
const seenIds = new Set();
|
|
65
|
+
const combined = [];
|
|
66
|
+
for (const r of memosResult.rows) {
|
|
67
|
+
const id = `memos:${r.id}`;
|
|
68
|
+
if (!seenIds.has(id)) {
|
|
69
|
+
seenIds.add(id);
|
|
70
|
+
combined.push({ ...r, source: 'memos' });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const r of memuKResult.rows) {
|
|
74
|
+
const id = `memuk:${r.id}`;
|
|
75
|
+
if (!seenIds.has(id)) {
|
|
76
|
+
seenIds.add(id);
|
|
77
|
+
combined.push({ ...r, source: 'memuK' });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 按时间倒序排列
|
|
82
|
+
combined.sort((a, b) => {
|
|
83
|
+
const ta = a.created_ts || a.happened_at || 0;
|
|
84
|
+
const tb = b.created_ts || b.happened_at || 0;
|
|
85
|
+
return tb - ta;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (!combined.length) {
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: 'text', text: `✅ memos_memuk_query "${query}" returned 0 results from both sources.` }],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const lines = combined.map((r, i) => {
|
|
95
|
+
let ts = 'unknown';
|
|
96
|
+
try {
|
|
97
|
+
const ms = typeof (r.created_ts || r.happened_at) === 'number'
|
|
98
|
+
? (r.created_ts || r.happened_at)
|
|
99
|
+
: parseInt(String(r.created_ts || r.happened_at));
|
|
100
|
+
if (ms > 1e12) {
|
|
101
|
+
ts = new Date(ms).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
|
|
102
|
+
} else if (ms > 1e9) {
|
|
103
|
+
ts = new Date(ms * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
|
|
104
|
+
}
|
|
105
|
+
} catch {}
|
|
106
|
+
const sourceTag = r.source === 'memos' ? '[memos]' : '[memuK]';
|
|
107
|
+
return `─── ${i + 1}. ${sourceTag} [id:${r.id}] ${ts} ───\n${r.content || r.summary || ''}`;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
content: [{
|
|
112
|
+
type: 'text',
|
|
113
|
+
text: `📋 Combined memos + memuK results for "${query}" (${combined.length}):\n\n${lines.join('\n\n')}`,
|
|
114
|
+
}],
|
|
115
|
+
};
|
|
116
|
+
}
|