supaclaw 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/LICENSE +21 -0
- package/README.md +871 -0
- package/SCHEMA.md +215 -0
- package/dist/clawdbot-integration.d.ts +171 -0
- package/dist/clawdbot-integration.js +339 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1815 -0
- package/dist/context-manager.d.ts +143 -0
- package/dist/context-manager.js +360 -0
- package/dist/error-handling.d.ts +100 -0
- package/dist/error-handling.js +301 -0
- package/dist/index.d.ts +735 -0
- package/dist/index.js +2256 -0
- package/dist/parsers.d.ts +115 -0
- package/dist/parsers.js +406 -0
- package/migrations/001_initial.sql +153 -0
- package/migrations/002_vector_search.sql +219 -0
- package/migrations/003_entity_relationships.sql +143 -0
- package/package.json +66 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Error handling and retry logic for OpenClaw Memory
|
|
4
|
+
*
|
|
5
|
+
* Provides:
|
|
6
|
+
* - Custom error types
|
|
7
|
+
* - Retry logic with exponential backoff
|
|
8
|
+
* - Circuit breaker pattern
|
|
9
|
+
* - Error recovery strategies
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CircuitBreaker = exports.RateLimitError = exports.ValidationError = exports.EmbeddingError = exports.DatabaseError = exports.OpenClawError = void 0;
|
|
13
|
+
exports.retry = retry;
|
|
14
|
+
exports.wrapDatabaseOperation = wrapDatabaseOperation;
|
|
15
|
+
exports.wrapEmbeddingOperation = wrapEmbeddingOperation;
|
|
16
|
+
exports.validateInput = validateInput;
|
|
17
|
+
exports.safeJsonParse = safeJsonParse;
|
|
18
|
+
exports.withTimeout = withTimeout;
|
|
19
|
+
exports.gracefulFallback = gracefulFallback;
|
|
20
|
+
exports.batchWithErrorHandling = batchWithErrorHandling;
|
|
21
|
+
// Custom error types
|
|
22
|
+
class OpenClawError extends Error {
|
|
23
|
+
code;
|
|
24
|
+
details;
|
|
25
|
+
constructor(message, code, details) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.code = code;
|
|
28
|
+
this.details = details;
|
|
29
|
+
this.name = 'OpenClawError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.OpenClawError = OpenClawError;
|
|
33
|
+
class DatabaseError extends OpenClawError {
|
|
34
|
+
constructor(message, details) {
|
|
35
|
+
super(message, 'DATABASE_ERROR', details);
|
|
36
|
+
this.name = 'DatabaseError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.DatabaseError = DatabaseError;
|
|
40
|
+
class EmbeddingError extends OpenClawError {
|
|
41
|
+
constructor(message, details) {
|
|
42
|
+
super(message, 'EMBEDDING_ERROR', details);
|
|
43
|
+
this.name = 'EmbeddingError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.EmbeddingError = EmbeddingError;
|
|
47
|
+
class ValidationError extends OpenClawError {
|
|
48
|
+
constructor(message, details) {
|
|
49
|
+
super(message, 'VALIDATION_ERROR', details);
|
|
50
|
+
this.name = 'ValidationError';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.ValidationError = ValidationError;
|
|
54
|
+
class RateLimitError extends OpenClawError {
|
|
55
|
+
constructor(message, details) {
|
|
56
|
+
super(message, 'RATE_LIMIT_ERROR', details);
|
|
57
|
+
this.name = 'RateLimitError';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.RateLimitError = RateLimitError;
|
|
61
|
+
const DEFAULT_RETRY_OPTIONS = {
|
|
62
|
+
maxAttempts: 3,
|
|
63
|
+
initialDelayMs: 1000,
|
|
64
|
+
maxDelayMs: 10000,
|
|
65
|
+
backoffMultiplier: 2,
|
|
66
|
+
shouldRetry: (error) => {
|
|
67
|
+
// Retry on network errors, rate limits, and transient database errors
|
|
68
|
+
if (error instanceof RateLimitError)
|
|
69
|
+
return true;
|
|
70
|
+
if (error instanceof DatabaseError) {
|
|
71
|
+
// Don't retry validation/constraint errors
|
|
72
|
+
const msg = error.message.toLowerCase();
|
|
73
|
+
return !msg.includes('constraint') && !msg.includes('invalid');
|
|
74
|
+
}
|
|
75
|
+
// Retry on network errors
|
|
76
|
+
if (error.message.includes('ECONNREFUSED'))
|
|
77
|
+
return true;
|
|
78
|
+
if (error.message.includes('ETIMEDOUT'))
|
|
79
|
+
return true;
|
|
80
|
+
if (error.message.includes('ENOTFOUND'))
|
|
81
|
+
return true;
|
|
82
|
+
return false;
|
|
83
|
+
},
|
|
84
|
+
onRetry: () => { }, // No-op by default
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Retry a function with exponential backoff
|
|
88
|
+
*/
|
|
89
|
+
async function retry(fn, options) {
|
|
90
|
+
const opts = { ...DEFAULT_RETRY_OPTIONS, ...options };
|
|
91
|
+
let lastError;
|
|
92
|
+
for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
|
|
93
|
+
try {
|
|
94
|
+
return await fn();
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
lastError = error;
|
|
98
|
+
// Don't retry if we've exhausted attempts or error is non-retryable
|
|
99
|
+
if (attempt >= opts.maxAttempts || !opts.shouldRetry(lastError)) {
|
|
100
|
+
throw lastError;
|
|
101
|
+
}
|
|
102
|
+
// Calculate delay with exponential backoff
|
|
103
|
+
const delay = Math.min(opts.initialDelayMs * Math.pow(opts.backoffMultiplier, attempt - 1), opts.maxDelayMs);
|
|
104
|
+
opts.onRetry(attempt, lastError);
|
|
105
|
+
// Wait before retrying
|
|
106
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
throw lastError;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Circuit breaker pattern
|
|
113
|
+
* Prevents cascading failures by failing fast when error rate is high
|
|
114
|
+
*/
|
|
115
|
+
class CircuitBreaker {
|
|
116
|
+
failureThreshold;
|
|
117
|
+
recoveryTimeMs;
|
|
118
|
+
successThreshold;
|
|
119
|
+
failures = 0;
|
|
120
|
+
successes = 0;
|
|
121
|
+
lastFailureTime = 0;
|
|
122
|
+
state = 'closed';
|
|
123
|
+
constructor(failureThreshold = 5, recoveryTimeMs = 60000, // 1 minute
|
|
124
|
+
successThreshold = 2) {
|
|
125
|
+
this.failureThreshold = failureThreshold;
|
|
126
|
+
this.recoveryTimeMs = recoveryTimeMs;
|
|
127
|
+
this.successThreshold = successThreshold;
|
|
128
|
+
}
|
|
129
|
+
async execute(fn) {
|
|
130
|
+
// If circuit is open, check if recovery time has passed
|
|
131
|
+
if (this.state === 'open') {
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
if (now - this.lastFailureTime > this.recoveryTimeMs) {
|
|
134
|
+
this.state = 'half-open';
|
|
135
|
+
this.successes = 0;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
throw new OpenClawError('Circuit breaker is open', 'CIRCUIT_BREAKER_OPEN', { failures: this.failures, lastFailureTime: this.lastFailureTime });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const result = await fn();
|
|
143
|
+
this.onSuccess();
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
this.onFailure();
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
onSuccess() {
|
|
152
|
+
this.failures = 0;
|
|
153
|
+
if (this.state === 'half-open') {
|
|
154
|
+
this.successes++;
|
|
155
|
+
if (this.successes >= this.successThreshold) {
|
|
156
|
+
this.state = 'closed';
|
|
157
|
+
this.successes = 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
onFailure() {
|
|
162
|
+
this.failures++;
|
|
163
|
+
this.lastFailureTime = Date.now();
|
|
164
|
+
if (this.failures >= this.failureThreshold) {
|
|
165
|
+
this.state = 'open';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
reset() {
|
|
169
|
+
this.failures = 0;
|
|
170
|
+
this.successes = 0;
|
|
171
|
+
this.lastFailureTime = 0;
|
|
172
|
+
this.state = 'closed';
|
|
173
|
+
}
|
|
174
|
+
getState() {
|
|
175
|
+
return {
|
|
176
|
+
state: this.state,
|
|
177
|
+
failures: this.failures,
|
|
178
|
+
successes: this.successes,
|
|
179
|
+
lastFailureTime: this.lastFailureTime,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
exports.CircuitBreaker = CircuitBreaker;
|
|
184
|
+
/**
|
|
185
|
+
* Wrap database operations with error handling
|
|
186
|
+
*/
|
|
187
|
+
function wrapDatabaseOperation(operation, errorContext) {
|
|
188
|
+
return retry(async () => {
|
|
189
|
+
try {
|
|
190
|
+
return await operation();
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
throw new DatabaseError(`${errorContext}: ${error.message}`, error);
|
|
194
|
+
}
|
|
195
|
+
}, {
|
|
196
|
+
maxAttempts: 3,
|
|
197
|
+
initialDelayMs: 500,
|
|
198
|
+
onRetry: (attempt, error) => {
|
|
199
|
+
console.warn(`Retrying ${errorContext} (attempt ${attempt}):`, error.message);
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Wrap embedding operations with error handling
|
|
205
|
+
*/
|
|
206
|
+
function wrapEmbeddingOperation(operation, errorContext) {
|
|
207
|
+
return retry(async () => {
|
|
208
|
+
try {
|
|
209
|
+
return await operation();
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
const err = error;
|
|
213
|
+
// Check for rate limit errors
|
|
214
|
+
if (err.message.includes('rate_limit') || err.message.includes('429')) {
|
|
215
|
+
throw new RateLimitError(`${errorContext}: Rate limit exceeded`, error);
|
|
216
|
+
}
|
|
217
|
+
throw new EmbeddingError(`${errorContext}: ${err.message}`, error);
|
|
218
|
+
}
|
|
219
|
+
}, {
|
|
220
|
+
maxAttempts: 3,
|
|
221
|
+
initialDelayMs: 2000, // Longer delay for API rate limits
|
|
222
|
+
backoffMultiplier: 3, // More aggressive backoff
|
|
223
|
+
onRetry: (attempt, error) => {
|
|
224
|
+
console.warn(`Retrying ${errorContext} (attempt ${attempt}):`, error.message);
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Validate inputs
|
|
230
|
+
*/
|
|
231
|
+
function validateInput(condition, message, details) {
|
|
232
|
+
if (!condition) {
|
|
233
|
+
throw new ValidationError(message, details);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Safe JSON parse
|
|
238
|
+
*/
|
|
239
|
+
function safeJsonParse(json, fallback) {
|
|
240
|
+
try {
|
|
241
|
+
return JSON.parse(json);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return fallback;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Safe async operation with timeout
|
|
249
|
+
*/
|
|
250
|
+
async function withTimeout(promise, timeoutMs, errorMessage = 'Operation timed out') {
|
|
251
|
+
return Promise.race([
|
|
252
|
+
promise,
|
|
253
|
+
new Promise((_, reject) => setTimeout(() => reject(new OpenClawError(errorMessage, 'TIMEOUT')), timeoutMs)),
|
|
254
|
+
]);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Graceful degradation helper
|
|
258
|
+
*/
|
|
259
|
+
async function gracefulFallback(primary, fallback, errorContext) {
|
|
260
|
+
try {
|
|
261
|
+
return await primary();
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
console.warn(`${errorContext} failed, using fallback:`, error.message);
|
|
265
|
+
return await fallback();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Batch operation with error handling
|
|
270
|
+
* Continues processing even if some items fail
|
|
271
|
+
*/
|
|
272
|
+
async function batchWithErrorHandling(items, operation, options) {
|
|
273
|
+
const opts = {
|
|
274
|
+
continueOnError: true,
|
|
275
|
+
onError: () => { },
|
|
276
|
+
...options,
|
|
277
|
+
};
|
|
278
|
+
const results = await Promise.allSettled(items.map(async (item) => ({
|
|
279
|
+
item,
|
|
280
|
+
result: await operation(item),
|
|
281
|
+
})));
|
|
282
|
+
return results.map((result, index) => {
|
|
283
|
+
const item = items[index];
|
|
284
|
+
if (result.status === 'fulfilled') {
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
result: result.value.result,
|
|
288
|
+
item,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const error = result.reason;
|
|
293
|
+
opts.onError(item, error);
|
|
294
|
+
return {
|
|
295
|
+
success: false,
|
|
296
|
+
error,
|
|
297
|
+
item,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|