@persistio/openclaw-plugin 0.1.7 → 0.2.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 +86 -53
- package/dist/capture.d.ts +17 -0
- package/dist/capture.js +112 -0
- package/dist/client.d.ts +35 -51
- package/dist/client.js +45 -70
- package/dist/config.d.ts +29 -0
- package/dist/config.js +86 -0
- package/dist/index.js +303 -623
- package/dist/memory-format.d.ts +8 -0
- package/dist/memory-format.js +121 -0
- package/openclaw.plugin.json +69 -95
- package/package.json +10 -11
- package/src/capture.ts +132 -0
- package/src/client.ts +72 -111
- package/src/config.ts +125 -0
- package/src/index.ts +308 -718
- package/src/memory-format.ts +127 -0
- package/dist/ingest-policy.d.ts +0 -48
- package/dist/ingest-policy.js +0 -380
- package/src/ingest-policy.ts +0 -508
package/src/client.ts
CHANGED
|
@@ -1,57 +1,45 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export interface PersistioConfig {
|
|
4
|
-
baseURL: string;
|
|
5
|
-
apiKey: string;
|
|
6
|
-
tokenBudget: number;
|
|
7
|
-
recallTopK: number;
|
|
8
|
-
recallMinSimilarity?: number;
|
|
9
|
-
recallTimeout: number;
|
|
10
|
-
ingest: PersistioIngestPolicy;
|
|
11
|
-
send: PersistioSendConfig;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export type PersistioSendRoleStatus = 'enabled' | 'disabled';
|
|
15
|
-
|
|
16
|
-
export interface PersistioSendConfig {
|
|
17
|
-
roles: {
|
|
18
|
-
user: PersistioSendRoleStatus;
|
|
19
|
-
agent: PersistioSendRoleStatus;
|
|
20
|
-
tool: PersistioSendRoleStatus;
|
|
21
|
-
};
|
|
22
|
-
}
|
|
1
|
+
import type { PersistioV2Config } from './config.js';
|
|
23
2
|
|
|
24
3
|
export interface PersistioMemory {
|
|
25
4
|
id: string;
|
|
26
5
|
data: string;
|
|
27
6
|
subject: string;
|
|
28
7
|
similarity?: number;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
export interface GetMemoryOptions {
|
|
34
|
-
includePending?: boolean;
|
|
8
|
+
confidence?: number;
|
|
9
|
+
categories?: string[];
|
|
10
|
+
source?: string;
|
|
11
|
+
edge_type?: string | null;
|
|
35
12
|
}
|
|
36
13
|
|
|
37
14
|
export interface RecallBundle {
|
|
38
15
|
global_user_rules?: string[];
|
|
39
|
-
user_rules
|
|
40
|
-
user_preferences
|
|
41
|
-
task_patterns
|
|
42
|
-
workflows
|
|
43
|
-
project
|
|
44
|
-
constraints
|
|
45
|
-
decisions
|
|
46
|
-
system_facts
|
|
47
|
-
domain_knowledge
|
|
16
|
+
user_rules?: string[];
|
|
17
|
+
user_preferences?: string[];
|
|
18
|
+
task_patterns?: string[];
|
|
19
|
+
workflows?: string[];
|
|
20
|
+
project?: string[];
|
|
21
|
+
constraints?: string[];
|
|
22
|
+
decisions?: string[];
|
|
23
|
+
system_facts?: string[];
|
|
24
|
+
domain_knowledge?: string[];
|
|
48
25
|
}
|
|
49
26
|
|
|
50
27
|
export interface RecallBundleResponse {
|
|
51
|
-
bundle
|
|
28
|
+
bundle?: RecallBundle;
|
|
52
29
|
related_bundle?: RecallBundle;
|
|
53
30
|
}
|
|
54
31
|
|
|
32
|
+
export interface RecallResult {
|
|
33
|
+
memories: PersistioMemory[];
|
|
34
|
+
relatedMemories: PersistioMemory[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface IngestChunk {
|
|
38
|
+
role: string;
|
|
39
|
+
content: string;
|
|
40
|
+
timestamp: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
55
43
|
export class PersistioTimeoutError extends Error {
|
|
56
44
|
constructor(operation: string, timeoutMs: number) {
|
|
57
45
|
super(`Persistio ${operation} timed out after ${timeoutMs}ms`);
|
|
@@ -62,70 +50,49 @@ export class PersistioTimeoutError extends Error {
|
|
|
62
50
|
export class PersistioClient {
|
|
63
51
|
private readonly baseURL: string;
|
|
64
52
|
private readonly apiKey: string;
|
|
65
|
-
private readonly recallTopK: number;
|
|
66
|
-
private readonly recallMinSimilarity?: number;
|
|
67
|
-
private readonly recallTimeout: number;
|
|
68
|
-
private readonly ingestTimeout: number;
|
|
69
|
-
private readonly writeTimeout: number;
|
|
70
53
|
|
|
71
|
-
constructor(config:
|
|
54
|
+
constructor(private readonly config: PersistioV2Config) {
|
|
72
55
|
this.baseURL = config.baseURL.replace(/\/$/, '');
|
|
73
56
|
this.apiKey = config.apiKey;
|
|
74
|
-
this.recallTopK = config.recallTopK;
|
|
75
|
-
this.recallMinSimilarity = config.recallMinSimilarity;
|
|
76
|
-
this.recallTimeout = config.recallTimeout;
|
|
77
|
-
this.ingestTimeout = config.ingest.timeoutMs;
|
|
78
|
-
this.writeTimeout = config.ingest.timeoutMs;
|
|
79
57
|
}
|
|
80
58
|
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
|
|
84
|
-
'Authorization': `Bearer ${this.apiKey}`,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async recall(query: string): Promise<PersistioMemory[]> {
|
|
89
|
-
return withRequestDeadline('recall', this.recallTimeout, async (signal) => {
|
|
90
|
-
const body: Record<string, unknown> = { query, top_k: this.recallTopK, include_pending: true };
|
|
91
|
-
if (typeof this.recallMinSimilarity === 'number') {
|
|
92
|
-
body.min_similarity = this.recallMinSimilarity;
|
|
93
|
-
}
|
|
94
|
-
|
|
59
|
+
async recall(query: string, options: { maxResults?: number } = {}): Promise<RecallResult> {
|
|
60
|
+
return withRequestDeadline('recall', this.config.recall.timeoutMs, async (signal) => {
|
|
61
|
+
const body = this.buildRecallBody(query, options.maxResults);
|
|
95
62
|
const res = await fetch(`${this.baseURL}/v1/recall`, {
|
|
96
63
|
method: 'POST',
|
|
97
64
|
headers: this.headers(),
|
|
98
65
|
body: JSON.stringify(body),
|
|
99
66
|
signal,
|
|
100
67
|
});
|
|
101
|
-
if (!res.ok) throw new Error(
|
|
102
|
-
const data = await res.json() as { memories
|
|
103
|
-
return
|
|
68
|
+
if (!res.ok) throw new Error(await formatHttpError('recall', res));
|
|
69
|
+
const data = await res.json() as { memories?: PersistioMemory[]; related_memories?: PersistioMemory[] };
|
|
70
|
+
return {
|
|
71
|
+
memories: Array.isArray(data.memories) ? data.memories : [],
|
|
72
|
+
relatedMemories: Array.isArray(data.related_memories) ? data.related_memories : [],
|
|
73
|
+
};
|
|
104
74
|
});
|
|
105
75
|
}
|
|
106
76
|
|
|
107
|
-
async recallBundle(query: string
|
|
108
|
-
return withRequestDeadline('recallBundle', this.
|
|
109
|
-
const body
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
77
|
+
async recallBundle(query: string): Promise<RecallBundleResponse> {
|
|
78
|
+
return withRequestDeadline('recallBundle', this.config.recall.timeoutMs, async (signal) => {
|
|
79
|
+
const body = {
|
|
80
|
+
...this.buildRecallBody(query, this.config.recall.maxResults),
|
|
81
|
+
};
|
|
114
82
|
const res = await fetch(`${this.baseURL}/v1/recall?format=bundle`, {
|
|
115
83
|
method: 'POST',
|
|
116
84
|
headers: this.headers(),
|
|
117
85
|
body: JSON.stringify(body),
|
|
118
86
|
signal,
|
|
119
87
|
});
|
|
120
|
-
if (!res.ok) throw new Error(
|
|
121
|
-
|
|
122
|
-
return data;
|
|
88
|
+
if (!res.ok) throw new Error(await formatHttpError('recallBundle', res));
|
|
89
|
+
return await res.json() as RecallBundleResponse;
|
|
123
90
|
});
|
|
124
91
|
}
|
|
125
92
|
|
|
126
|
-
async ingest(sessionId: string, chunks:
|
|
93
|
+
async ingest(sessionId: string, chunks: IngestChunk[]): Promise<void> {
|
|
127
94
|
if (chunks.length === 0) return;
|
|
128
|
-
await withRequestDeadline('ingest', this.
|
|
95
|
+
await withRequestDeadline('ingest', this.config.capture.timeoutMs, async (signal) => {
|
|
129
96
|
const res = await fetch(`${this.baseURL}/v1/ingest`, {
|
|
130
97
|
method: 'POST',
|
|
131
98
|
headers: this.headers(),
|
|
@@ -136,56 +103,52 @@ export class PersistioClient {
|
|
|
136
103
|
});
|
|
137
104
|
}
|
|
138
105
|
|
|
139
|
-
async
|
|
140
|
-
|
|
106
|
+
async storeMemory(data: string, subject: string): Promise<PersistioMemory> {
|
|
107
|
+
return withRequestDeadline('memory_store', this.config.capture.timeoutMs, async (signal) => {
|
|
141
108
|
const res = await fetch(`${this.baseURL}/v1/memories`, {
|
|
142
109
|
method: 'POST',
|
|
143
110
|
headers: this.headers(),
|
|
144
111
|
body: JSON.stringify({ data, subject }),
|
|
145
112
|
signal,
|
|
146
113
|
});
|
|
147
|
-
if (!res.ok) throw new Error(
|
|
114
|
+
if (!res.ok) throw new Error(await formatHttpError('memory_store', res));
|
|
115
|
+
return await res.json() as PersistioMemory;
|
|
148
116
|
});
|
|
149
117
|
}
|
|
150
118
|
|
|
151
|
-
async
|
|
152
|
-
await withRequestDeadline('
|
|
153
|
-
const res = await fetch(`${this.baseURL}/v1/memories/${id}`, {
|
|
119
|
+
async forgetMemory(id: string): Promise<void> {
|
|
120
|
+
await withRequestDeadline('memory_forget', this.config.capture.timeoutMs, async (signal) => {
|
|
121
|
+
const res = await fetch(`${this.baseURL}/v1/memories/${encodeURIComponent(id)}`, {
|
|
154
122
|
method: 'DELETE',
|
|
155
123
|
headers: this.headers(),
|
|
156
124
|
signal,
|
|
157
125
|
});
|
|
158
|
-
if (!res.ok) throw new Error(
|
|
126
|
+
if (!res.ok) throw new Error(await formatHttpError('memory_forget', res));
|
|
159
127
|
});
|
|
160
128
|
}
|
|
161
129
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
130
|
+
private buildRecallBody(query: string, maxResults = this.config.recall.maxResults): Record<string, unknown> {
|
|
131
|
+
const body: Record<string, unknown> = {
|
|
132
|
+
query,
|
|
133
|
+
top_k: maxResults,
|
|
134
|
+
include_pending: this.config.recall.includePending,
|
|
135
|
+
include_related: this.config.recall.includeRelated,
|
|
136
|
+
};
|
|
137
|
+
if (typeof this.config.recall.minSimilarity === 'number') {
|
|
138
|
+
body.min_similarity = this.config.recall.minSimilarity;
|
|
139
|
+
}
|
|
140
|
+
return body;
|
|
173
141
|
}
|
|
174
142
|
|
|
175
|
-
|
|
176
|
-
return
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
});
|
|
181
|
-
if (!res.ok) throw new Error(`Persistio listMemories failed: ${res.status}`);
|
|
182
|
-
const data = await res.json() as { items: PersistioMemory[] };
|
|
183
|
-
return data.items ?? [];
|
|
184
|
-
});
|
|
143
|
+
private headers(): Record<string, string> {
|
|
144
|
+
return {
|
|
145
|
+
'Content-Type': 'application/json',
|
|
146
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
147
|
+
};
|
|
185
148
|
}
|
|
186
149
|
}
|
|
187
150
|
|
|
188
|
-
async function withRequestDeadline<T>(
|
|
151
|
+
export async function withRequestDeadline<T>(
|
|
189
152
|
operation: string,
|
|
190
153
|
timeoutMs: number,
|
|
191
154
|
run: (signal: AbortSignal) => Promise<T>,
|
|
@@ -196,7 +159,6 @@ async function withRequestDeadline<T>(
|
|
|
196
159
|
|
|
197
160
|
const controller = new AbortController();
|
|
198
161
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
199
|
-
|
|
200
162
|
const deadline = new Promise<never>((_resolve, reject) => {
|
|
201
163
|
timeout = setTimeout(() => {
|
|
202
164
|
controller.abort();
|
|
@@ -217,8 +179,7 @@ async function withRequestDeadline<T>(
|
|
|
217
179
|
}
|
|
218
180
|
|
|
219
181
|
function isAbortLikeError(err: unknown): boolean {
|
|
220
|
-
|
|
221
|
-
return err.name === 'AbortError' || err.name === 'TimeoutError';
|
|
182
|
+
return err instanceof Error && (err.name === 'AbortError' || err.name === 'TimeoutError');
|
|
222
183
|
}
|
|
223
184
|
|
|
224
185
|
async function formatHttpError(operation: string, res: Response): Promise<string> {
|
|
@@ -226,7 +187,7 @@ async function formatHttpError(operation: string, res: Response): Promise<string
|
|
|
226
187
|
try {
|
|
227
188
|
detail = (await res.text()).trim().slice(0, 500);
|
|
228
189
|
} catch {
|
|
229
|
-
//
|
|
190
|
+
// Status code is still useful if the body cannot be read.
|
|
230
191
|
}
|
|
231
192
|
|
|
232
193
|
return detail
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export type PersistioCaptureRoleStatus = 'enabled' | 'bounded' | 'disabled';
|
|
2
|
+
|
|
3
|
+
export interface PersistioV2Config {
|
|
4
|
+
baseURL: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
autoRecall: boolean;
|
|
7
|
+
autoCapture: boolean;
|
|
8
|
+
recall: {
|
|
9
|
+
timeoutMs: number;
|
|
10
|
+
maxResults: number;
|
|
11
|
+
tokenBudget: number;
|
|
12
|
+
minSimilarity?: number;
|
|
13
|
+
includePending: boolean;
|
|
14
|
+
includeRelated: boolean;
|
|
15
|
+
queryMaxChars: number;
|
|
16
|
+
};
|
|
17
|
+
capture: {
|
|
18
|
+
timeoutMs: number;
|
|
19
|
+
maxCharsPerTurn: number;
|
|
20
|
+
maxCharsPerMessage: number;
|
|
21
|
+
maxChunksPerTurn: number;
|
|
22
|
+
maxChunkChars: number;
|
|
23
|
+
roles: {
|
|
24
|
+
user: 'enabled' | 'disabled';
|
|
25
|
+
assistant: PersistioCaptureRoleStatus;
|
|
26
|
+
tool: 'enabled' | 'disabled';
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DEFAULT_CONFIG: PersistioV2Config = {
|
|
32
|
+
baseURL: '',
|
|
33
|
+
apiKey: '',
|
|
34
|
+
autoRecall: true,
|
|
35
|
+
autoCapture: true,
|
|
36
|
+
recall: {
|
|
37
|
+
timeoutMs: 1200,
|
|
38
|
+
maxResults: 4,
|
|
39
|
+
tokenBudget: 400,
|
|
40
|
+
includePending: false,
|
|
41
|
+
includeRelated: false,
|
|
42
|
+
queryMaxChars: 1200,
|
|
43
|
+
},
|
|
44
|
+
capture: {
|
|
45
|
+
timeoutMs: 10000,
|
|
46
|
+
maxCharsPerTurn: 6000,
|
|
47
|
+
maxCharsPerMessage: 3000,
|
|
48
|
+
maxChunksPerTurn: 4,
|
|
49
|
+
maxChunkChars: 2000,
|
|
50
|
+
roles: {
|
|
51
|
+
user: 'enabled',
|
|
52
|
+
assistant: 'bounded',
|
|
53
|
+
tool: 'disabled',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function readObject(value: unknown): Record<string, unknown> {
|
|
59
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
60
|
+
? value as Record<string, unknown>
|
|
61
|
+
: {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function readString(value: unknown, fallback = ''): string {
|
|
65
|
+
return typeof value === 'string' ? value : fallback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function readBoolean(value: unknown, fallback: boolean): boolean {
|
|
69
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readPositiveInteger(value: unknown, fallback: number, min = 1): number {
|
|
73
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= min
|
|
74
|
+
? Math.floor(value)
|
|
75
|
+
: fallback;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readSimilarity(value: unknown): number | undefined {
|
|
79
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1
|
|
80
|
+
? value
|
|
81
|
+
: undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readEnabledDisabled(value: unknown, fallback: 'enabled' | 'disabled'): 'enabled' | 'disabled' {
|
|
85
|
+
return value === 'enabled' || value === 'disabled' ? value : fallback;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function readAssistantRole(value: unknown, fallback: PersistioCaptureRoleStatus): PersistioCaptureRoleStatus {
|
|
89
|
+
return value === 'enabled' || value === 'bounded' || value === 'disabled' ? value : fallback;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function resolveConfig(raw: unknown): PersistioV2Config {
|
|
93
|
+
const input = readObject(raw);
|
|
94
|
+
const recall = readObject(input['recall']);
|
|
95
|
+
const capture = readObject(input['capture']);
|
|
96
|
+
const roles = readObject(capture['roles']);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
baseURL: readString(input['baseURL'], DEFAULT_CONFIG.baseURL),
|
|
100
|
+
apiKey: readString(input['apiKey'], DEFAULT_CONFIG.apiKey),
|
|
101
|
+
autoRecall: readBoolean(input['autoRecall'], DEFAULT_CONFIG.autoRecall),
|
|
102
|
+
autoCapture: readBoolean(input['autoCapture'], DEFAULT_CONFIG.autoCapture),
|
|
103
|
+
recall: {
|
|
104
|
+
timeoutMs: readPositiveInteger(recall['timeoutMs'], DEFAULT_CONFIG.recall.timeoutMs),
|
|
105
|
+
maxResults: readPositiveInteger(recall['maxResults'], DEFAULT_CONFIG.recall.maxResults),
|
|
106
|
+
tokenBudget: readPositiveInteger(recall['tokenBudget'], DEFAULT_CONFIG.recall.tokenBudget),
|
|
107
|
+
minSimilarity: readSimilarity(recall['minSimilarity']),
|
|
108
|
+
includePending: readBoolean(recall['includePending'], DEFAULT_CONFIG.recall.includePending),
|
|
109
|
+
includeRelated: readBoolean(recall['includeRelated'], DEFAULT_CONFIG.recall.includeRelated),
|
|
110
|
+
queryMaxChars: readPositiveInteger(recall['queryMaxChars'], DEFAULT_CONFIG.recall.queryMaxChars, 100),
|
|
111
|
+
},
|
|
112
|
+
capture: {
|
|
113
|
+
timeoutMs: readPositiveInteger(capture['timeoutMs'], DEFAULT_CONFIG.capture.timeoutMs),
|
|
114
|
+
maxCharsPerTurn: readPositiveInteger(capture['maxCharsPerTurn'], DEFAULT_CONFIG.capture.maxCharsPerTurn),
|
|
115
|
+
maxCharsPerMessage: readPositiveInteger(capture['maxCharsPerMessage'], DEFAULT_CONFIG.capture.maxCharsPerMessage),
|
|
116
|
+
maxChunksPerTurn: readPositiveInteger(capture['maxChunksPerTurn'], DEFAULT_CONFIG.capture.maxChunksPerTurn),
|
|
117
|
+
maxChunkChars: readPositiveInteger(capture['maxChunkChars'], DEFAULT_CONFIG.capture.maxChunkChars, 256),
|
|
118
|
+
roles: {
|
|
119
|
+
user: readEnabledDisabled(roles['user'], DEFAULT_CONFIG.capture.roles.user),
|
|
120
|
+
assistant: readAssistantRole(roles['assistant'], DEFAULT_CONFIG.capture.roles.assistant),
|
|
121
|
+
tool: readEnabledDisabled(roles['tool'], DEFAULT_CONFIG.capture.roles.tool),
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|