@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 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 = new PersistioClient(cfg);
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;
@@ -1,8 +1,8 @@
1
1
  {
2
- "id": "@persistio/openclaw-plugin",
2
+ "id": "openclaw-persistio",
3
3
  "name": "Persistio Memory",
4
4
  "description": "Persistent semantic memory for OpenClaw via Persistio",
5
- "version": "0.1.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.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 = new PersistioClient(cfg);
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) {