@persistio/openclaw-plugin 0.1.1 → 0.1.3
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.js +129 -3
- package/openclaw.plugin.json +3 -3
- package/package.json +2 -2
- package/src/index.ts +169 -4
package/dist/index.js
CHANGED
|
@@ -143,6 +143,127 @@ function extractTextFromMessage(msg) {
|
|
|
143
143
|
}
|
|
144
144
|
return parts.length > 0 ? parts.join(' ') : null;
|
|
145
145
|
}
|
|
146
|
+
const PERSISTIO_MEMORY_PATH_PREFIX = 'persistio://memory/';
|
|
147
|
+
function createClient(config, recallTopK = config.recallTopK) {
|
|
148
|
+
return new PersistioClient({ ...config, recallTopK });
|
|
149
|
+
}
|
|
150
|
+
function normalizeMemoryScore(memory) {
|
|
151
|
+
if (typeof memory.similarity === 'number' && Number.isFinite(memory.similarity)) {
|
|
152
|
+
return memory.similarity;
|
|
153
|
+
}
|
|
154
|
+
if (Number.isFinite(memory.confidence)) {
|
|
155
|
+
return memory.confidence > 1 ? memory.confidence / 100 : memory.confidence;
|
|
156
|
+
}
|
|
157
|
+
return 0;
|
|
158
|
+
}
|
|
159
|
+
function buildMemoryPath(id) {
|
|
160
|
+
return `${PERSISTIO_MEMORY_PATH_PREFIX}${id}`;
|
|
161
|
+
}
|
|
162
|
+
function parseMemoryPath(relPath) {
|
|
163
|
+
return relPath.startsWith(PERSISTIO_MEMORY_PATH_PREFIX)
|
|
164
|
+
? relPath.slice(PERSISTIO_MEMORY_PATH_PREFIX.length)
|
|
165
|
+
: null;
|
|
166
|
+
}
|
|
167
|
+
function formatMemoryDocument(memory) {
|
|
168
|
+
const lines = [
|
|
169
|
+
`Subject: ${memory.subject}`,
|
|
170
|
+
`Memory ID: ${memory.id}`,
|
|
171
|
+
`Confidence: ${memory.confidence}`,
|
|
172
|
+
];
|
|
173
|
+
if (memory.categories.length > 0) {
|
|
174
|
+
lines.push(`Categories: ${memory.categories.join(', ')}`);
|
|
175
|
+
}
|
|
176
|
+
lines.push('', memory.data);
|
|
177
|
+
return lines.join('\n');
|
|
178
|
+
}
|
|
179
|
+
async function probePersistio(client) {
|
|
180
|
+
try {
|
|
181
|
+
await client.recall('__openclaw_probe__');
|
|
182
|
+
return { ok: true };
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
return { ok: false, error: String(err) };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function createMemorySearchManager(config) {
|
|
189
|
+
const client = createClient(config);
|
|
190
|
+
return {
|
|
191
|
+
async search(query, opts) {
|
|
192
|
+
if (opts?.sources && !opts.sources.includes('memory')) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
const recallTopK = typeof opts?.maxResults === 'number' ? opts.maxResults : config.recallTopK;
|
|
196
|
+
const recallClient = createClient(config, recallTopK);
|
|
197
|
+
const memories = await recallClient.recall(query);
|
|
198
|
+
return memories
|
|
199
|
+
.map((memory) => {
|
|
200
|
+
const score = normalizeMemoryScore(memory);
|
|
201
|
+
return {
|
|
202
|
+
path: buildMemoryPath(memory.id),
|
|
203
|
+
startLine: 1,
|
|
204
|
+
endLine: 1,
|
|
205
|
+
score,
|
|
206
|
+
vectorScore: typeof memory.similarity === 'number' ? memory.similarity : undefined,
|
|
207
|
+
snippet: truncate(memory.data, 400),
|
|
208
|
+
source: 'memory',
|
|
209
|
+
citation: memory.subject,
|
|
210
|
+
};
|
|
211
|
+
})
|
|
212
|
+
.filter((result) => opts?.minScore === undefined || result.score >= opts.minScore);
|
|
213
|
+
},
|
|
214
|
+
async readFile(params) {
|
|
215
|
+
const memoryId = parseMemoryPath(params.relPath);
|
|
216
|
+
if (!memoryId) {
|
|
217
|
+
throw new Error(`Unsupported Persistio memory path: ${params.relPath}`);
|
|
218
|
+
}
|
|
219
|
+
const memories = await client.listMemories();
|
|
220
|
+
const memory = memories.find((item) => item.id === memoryId);
|
|
221
|
+
if (!memory) {
|
|
222
|
+
throw new Error(`Persistio memory not found: ${memoryId}`);
|
|
223
|
+
}
|
|
224
|
+
const text = formatMemoryDocument(memory);
|
|
225
|
+
return {
|
|
226
|
+
path: params.relPath,
|
|
227
|
+
text,
|
|
228
|
+
truncated: false,
|
|
229
|
+
from: params.from ?? 1,
|
|
230
|
+
lines: params.lines,
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
status() {
|
|
234
|
+
return {
|
|
235
|
+
backend: 'builtin',
|
|
236
|
+
provider: 'persistio',
|
|
237
|
+
sources: ['memory'],
|
|
238
|
+
vector: {
|
|
239
|
+
enabled: true,
|
|
240
|
+
},
|
|
241
|
+
custom: {
|
|
242
|
+
baseURL: config.baseURL,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
async probeEmbeddingAvailability() {
|
|
247
|
+
return probePersistio(client);
|
|
248
|
+
},
|
|
249
|
+
async probeVectorAvailability() {
|
|
250
|
+
const probe = await probePersistio(client);
|
|
251
|
+
return probe.ok;
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function createMemoryRuntime(config) {
|
|
256
|
+
return {
|
|
257
|
+
async getMemorySearchManager() {
|
|
258
|
+
return {
|
|
259
|
+
manager: createMemorySearchManager(config),
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
resolveMemoryBackendConfig() {
|
|
263
|
+
return { backend: 'builtin' };
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
146
267
|
export default definePluginEntry({
|
|
147
268
|
id: 'openclaw-persistio',
|
|
148
269
|
name: 'Persistio Memory',
|
|
@@ -153,7 +274,10 @@ export default definePluginEntry({
|
|
|
153
274
|
api.logger?.warn?.('openclaw-persistio: baseURL and apiKey are required. Plugin disabled.');
|
|
154
275
|
return;
|
|
155
276
|
}
|
|
156
|
-
const client =
|
|
277
|
+
const client = createClient(cfg);
|
|
278
|
+
api.registerMemoryCapability({
|
|
279
|
+
runtime: createMemoryRuntime(cfg),
|
|
280
|
+
});
|
|
157
281
|
// -------------------------------------------------------------------------
|
|
158
282
|
// before_prompt_build — recall relevant memories and inject into context
|
|
159
283
|
// Event: { prompt: string, messages: unknown[] }
|
|
@@ -177,9 +301,11 @@ export default definePluginEntry({
|
|
|
177
301
|
// Event: { runId?, messages: unknown[], success: boolean, error?, durationMs? }
|
|
178
302
|
// Observation only — no return value.
|
|
179
303
|
// -------------------------------------------------------------------------
|
|
180
|
-
api.on('agent_end', async (event) => {
|
|
304
|
+
api.on('agent_end', async (event, context) => {
|
|
181
305
|
try {
|
|
182
|
-
const sessionId = event.runId ?? 'unknown-session';
|
|
306
|
+
const sessionId = context?.sessionId ?? event.runId ?? 'unknown-session';
|
|
307
|
+
if (sessionId.startsWith('announce:'))
|
|
308
|
+
return;
|
|
183
309
|
const chunks = [];
|
|
184
310
|
for (const msg of event.messages) {
|
|
185
311
|
const m = msg;
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "
|
|
2
|
+
"id": "openclaw-persistio",
|
|
3
3
|
"name": "Persistio Memory",
|
|
4
4
|
"description": "Persistent semantic memory for OpenClaw via Persistio",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.3",
|
|
6
6
|
"kind": "memory",
|
|
7
7
|
"activation": {
|
|
8
8
|
"onStartup": true
|
|
@@ -40,4 +40,4 @@
|
|
|
40
40
|
"apiKey"
|
|
41
41
|
]
|
|
42
42
|
}
|
|
43
|
-
}
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@persistio/openclaw-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "OpenClaw plugin for Persistio \u2014 persistent semantic memory for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,4 +53,4 @@
|
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"openclaw": ">=2026.3.24-beta.2"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
|
|
2
|
+
import type {
|
|
3
|
+
MemoryEmbeddingProbeResult,
|
|
4
|
+
MemoryProviderStatus,
|
|
5
|
+
MemorySearchManager,
|
|
6
|
+
MemorySearchResult,
|
|
7
|
+
} from 'openclaw/plugin-sdk/memory-core-host-engine-storage';
|
|
2
8
|
import { Type } from '@sinclair/typebox';
|
|
3
|
-
import { PersistioClient, type PersistioConfig, type RecallBundle } from './client.js';
|
|
9
|
+
import { PersistioClient, type PersistioConfig, type PersistioMemory, type RecallBundle } from './client.js';
|
|
4
10
|
|
|
5
11
|
function resolveConfig(raw: unknown): PersistioConfig {
|
|
6
12
|
const c = (raw ?? {}) as Record<string, unknown>;
|
|
@@ -154,6 +160,161 @@ function extractTextFromMessage(msg: unknown): string | null {
|
|
|
154
160
|
return parts.length > 0 ? parts.join(' ') : null;
|
|
155
161
|
}
|
|
156
162
|
|
|
163
|
+
const PERSISTIO_MEMORY_PATH_PREFIX = 'persistio://memory/';
|
|
164
|
+
|
|
165
|
+
function createClient(config: PersistioConfig, recallTopK = config.recallTopK): PersistioClient {
|
|
166
|
+
return new PersistioClient({ ...config, recallTopK });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function normalizeMemoryScore(memory: PersistioMemory): number {
|
|
170
|
+
if (typeof memory.similarity === 'number' && Number.isFinite(memory.similarity)) {
|
|
171
|
+
return memory.similarity;
|
|
172
|
+
}
|
|
173
|
+
if (Number.isFinite(memory.confidence)) {
|
|
174
|
+
return memory.confidence > 1 ? memory.confidence / 100 : memory.confidence;
|
|
175
|
+
}
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function buildMemoryPath(id: string): string {
|
|
180
|
+
return `${PERSISTIO_MEMORY_PATH_PREFIX}${id}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseMemoryPath(relPath: string): string | null {
|
|
184
|
+
return relPath.startsWith(PERSISTIO_MEMORY_PATH_PREFIX)
|
|
185
|
+
? relPath.slice(PERSISTIO_MEMORY_PATH_PREFIX.length)
|
|
186
|
+
: null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function formatMemoryDocument(memory: PersistioMemory): string {
|
|
190
|
+
const lines = [
|
|
191
|
+
`Subject: ${memory.subject}`,
|
|
192
|
+
`Memory ID: ${memory.id}`,
|
|
193
|
+
`Confidence: ${memory.confidence}`,
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
if (memory.categories.length > 0) {
|
|
197
|
+
lines.push(`Categories: ${memory.categories.join(', ')}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
lines.push('', memory.data);
|
|
201
|
+
return lines.join('\n');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function probePersistio(client: PersistioClient): Promise<MemoryEmbeddingProbeResult> {
|
|
205
|
+
try {
|
|
206
|
+
await client.recall('__openclaw_probe__');
|
|
207
|
+
return { ok: true };
|
|
208
|
+
} catch (err) {
|
|
209
|
+
return { ok: false, error: String(err) };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function createMemorySearchManager(config: PersistioConfig): MemorySearchManager {
|
|
214
|
+
const client = createClient(config);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
async search(
|
|
218
|
+
query: string,
|
|
219
|
+
opts?: {
|
|
220
|
+
maxResults?: number;
|
|
221
|
+
minScore?: number;
|
|
222
|
+
sessionKey?: string;
|
|
223
|
+
qmdSearchModeOverride?: 'query' | 'search' | 'vsearch';
|
|
224
|
+
onDebug?: (debug: unknown) => void;
|
|
225
|
+
sources?: Array<'memory' | 'sessions'>;
|
|
226
|
+
},
|
|
227
|
+
) {
|
|
228
|
+
if (opts?.sources && !opts.sources.includes('memory')) {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const recallTopK = typeof opts?.maxResults === 'number' ? opts.maxResults : config.recallTopK;
|
|
233
|
+
const recallClient = createClient(config, recallTopK);
|
|
234
|
+
const memories = await recallClient.recall(query);
|
|
235
|
+
|
|
236
|
+
return memories
|
|
237
|
+
.map((memory): MemorySearchResult => {
|
|
238
|
+
const score = normalizeMemoryScore(memory);
|
|
239
|
+
return {
|
|
240
|
+
path: buildMemoryPath(memory.id),
|
|
241
|
+
startLine: 1,
|
|
242
|
+
endLine: 1,
|
|
243
|
+
score,
|
|
244
|
+
vectorScore: typeof memory.similarity === 'number' ? memory.similarity : undefined,
|
|
245
|
+
snippet: truncate(memory.data, 400),
|
|
246
|
+
source: 'memory',
|
|
247
|
+
citation: memory.subject,
|
|
248
|
+
};
|
|
249
|
+
})
|
|
250
|
+
.filter((result) => opts?.minScore === undefined || result.score >= opts.minScore);
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
async readFile(params: {
|
|
254
|
+
relPath: string;
|
|
255
|
+
from?: number;
|
|
256
|
+
lines?: number;
|
|
257
|
+
}) {
|
|
258
|
+
const memoryId = parseMemoryPath(params.relPath);
|
|
259
|
+
if (!memoryId) {
|
|
260
|
+
throw new Error(`Unsupported Persistio memory path: ${params.relPath}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const memories = await client.listMemories();
|
|
264
|
+
const memory = memories.find((item) => item.id === memoryId);
|
|
265
|
+
if (!memory) {
|
|
266
|
+
throw new Error(`Persistio memory not found: ${memoryId}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const text = formatMemoryDocument(memory);
|
|
270
|
+
return {
|
|
271
|
+
path: params.relPath,
|
|
272
|
+
text,
|
|
273
|
+
truncated: false,
|
|
274
|
+
from: params.from ?? 1,
|
|
275
|
+
lines: params.lines,
|
|
276
|
+
};
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
status(): MemoryProviderStatus {
|
|
280
|
+
return {
|
|
281
|
+
backend: 'builtin',
|
|
282
|
+
provider: 'persistio',
|
|
283
|
+
sources: ['memory'],
|
|
284
|
+
vector: {
|
|
285
|
+
enabled: true,
|
|
286
|
+
},
|
|
287
|
+
custom: {
|
|
288
|
+
baseURL: config.baseURL,
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
async probeEmbeddingAvailability() {
|
|
294
|
+
return probePersistio(client);
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
async probeVectorAvailability() {
|
|
298
|
+
const probe = await probePersistio(client);
|
|
299
|
+
return probe.ok;
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function createMemoryRuntime(config: PersistioConfig) {
|
|
305
|
+
return {
|
|
306
|
+
async getMemorySearchManager() {
|
|
307
|
+
return {
|
|
308
|
+
manager: createMemorySearchManager(config),
|
|
309
|
+
};
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
resolveMemoryBackendConfig() {
|
|
313
|
+
return { backend: 'builtin' as const };
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
157
318
|
export default definePluginEntry({
|
|
158
319
|
id: 'openclaw-persistio',
|
|
159
320
|
name: 'Persistio Memory',
|
|
@@ -167,7 +328,10 @@ export default definePluginEntry({
|
|
|
167
328
|
return;
|
|
168
329
|
}
|
|
169
330
|
|
|
170
|
-
const client =
|
|
331
|
+
const client = createClient(cfg);
|
|
332
|
+
api.registerMemoryCapability({
|
|
333
|
+
runtime: createMemoryRuntime(cfg),
|
|
334
|
+
});
|
|
171
335
|
|
|
172
336
|
// -------------------------------------------------------------------------
|
|
173
337
|
// before_prompt_build — recall relevant memories and inject into context
|
|
@@ -191,9 +355,10 @@ export default definePluginEntry({
|
|
|
191
355
|
// Event: { runId?, messages: unknown[], success: boolean, error?, durationMs? }
|
|
192
356
|
// Observation only — no return value.
|
|
193
357
|
// -------------------------------------------------------------------------
|
|
194
|
-
api.on('agent_end', async (event) => {
|
|
358
|
+
api.on('agent_end', async (event, context) => {
|
|
195
359
|
try {
|
|
196
|
-
const sessionId = event.runId ?? 'unknown-session';
|
|
360
|
+
const sessionId = context?.sessionId ?? event.runId ?? 'unknown-session';
|
|
361
|
+
if (sessionId.startsWith('announce:')) return;
|
|
197
362
|
const chunks: Array<{ role: string; content: string; timestamp: string }> = [];
|
|
198
363
|
|
|
199
364
|
for (const msg of event.messages) {
|