@memoryrelay/plugin-memoryrelay-ai 0.15.7 → 0.16.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/index.ts +493 -4868
- package/openclaw.plugin.json +41 -3
- package/package.json +1 -1
- package/src/client/memoryrelay-client.ts +816 -0
- package/src/context/namespace-router.ts +19 -0
- package/src/context/request-context.ts +39 -0
- package/src/context/session-resolver.ts +93 -0
- package/src/filters/content-patterns.ts +32 -0
- package/src/filters/noise-patterns.ts +33 -0
- package/src/filters/non-interactive.ts +30 -0
- package/src/hooks/activity.ts +51 -0
- package/src/hooks/agent-end.ts +48 -0
- package/src/hooks/before-agent-start.ts +109 -0
- package/src/hooks/before-prompt-build.ts +46 -0
- package/src/hooks/compaction.ts +51 -0
- package/src/hooks/privacy.ts +44 -0
- package/src/hooks/session-lifecycle.ts +47 -0
- package/src/hooks/subagent.ts +62 -0
- package/src/pipelines/capture/content-strip.ts +14 -0
- package/src/pipelines/capture/dedup.ts +17 -0
- package/src/pipelines/capture/index.ts +13 -0
- package/src/pipelines/capture/message-filter.ts +16 -0
- package/src/pipelines/capture/store.ts +33 -0
- package/src/pipelines/capture/trigger-gate.ts +21 -0
- package/src/pipelines/capture/truncate.ts +16 -0
- package/src/pipelines/recall/format.ts +30 -0
- package/src/pipelines/recall/index.ts +12 -0
- package/src/pipelines/recall/rank.ts +40 -0
- package/src/pipelines/recall/scope-resolver.ts +20 -0
- package/src/pipelines/recall/search.ts +43 -0
- package/src/pipelines/recall/trigger-gate.ts +17 -0
- package/src/pipelines/runner.ts +25 -0
- package/src/pipelines/types.ts +157 -0
- package/src/tools/agent-tools.ts +127 -0
- package/src/tools/decision-tools.ts +309 -0
- package/src/tools/entity-tools.ts +215 -0
- package/src/tools/health-tools.ts +42 -0
- package/src/tools/memory-tools.ts +690 -0
- package/src/tools/pattern-tools.ts +250 -0
- package/src/tools/project-tools.ts +444 -0
- package/src/tools/session-tools.ts +195 -0
- package/src/tools/v2-tools.ts +228 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryRelay API Client
|
|
3
|
+
*
|
|
4
|
+
* Extracted from index.ts — provides typed HTTP access to the MemoryRelay API
|
|
5
|
+
* with timeout, retry, and debug/status instrumentation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DebugLogger } from "../debug-logger.js";
|
|
9
|
+
import type { StatusReporter } from "../status-reporter.js";
|
|
10
|
+
import type { Memory, MemoryRelayClient as IMemoryRelayClient } from "../pipelines/types.js";
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Constants
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_API_URL = "https://api.memoryrelay.net";
|
|
17
|
+
export const REQUEST_TIMEOUT_MS = 30000; // 30 seconds
|
|
18
|
+
export const MAX_RETRIES = 3;
|
|
19
|
+
export const INITIAL_RETRY_DELAY_MS = 1000; // 1 second
|
|
20
|
+
export const VALID_HEALTH_STATUSES = ["ok", "healthy", "up"];
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Types
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
// Re-export Memory from canonical source
|
|
27
|
+
export type { Memory } from "../pipelines/types.js";
|
|
28
|
+
|
|
29
|
+
export interface SearchResult {
|
|
30
|
+
memory: Memory;
|
|
31
|
+
score: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface Stats {
|
|
35
|
+
total_memories: number;
|
|
36
|
+
last_updated?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Utility Functions
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sleep for specified milliseconds
|
|
45
|
+
*/
|
|
46
|
+
function sleep(ms: number): Promise<void> {
|
|
47
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if error is retryable (network/timeout errors)
|
|
52
|
+
*/
|
|
53
|
+
function isRetryableError(error: unknown): boolean {
|
|
54
|
+
const errStr = String(error).toLowerCase();
|
|
55
|
+
return (
|
|
56
|
+
errStr.includes("timeout") ||
|
|
57
|
+
errStr.includes("econnrefused") ||
|
|
58
|
+
errStr.includes("enotfound") ||
|
|
59
|
+
errStr.includes("network") ||
|
|
60
|
+
errStr.includes("fetch failed") ||
|
|
61
|
+
errStr.includes("502") ||
|
|
62
|
+
errStr.includes("503") ||
|
|
63
|
+
errStr.includes("504")
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Fetch with timeout
|
|
69
|
+
*/
|
|
70
|
+
export async function fetchWithTimeout(
|
|
71
|
+
url: string,
|
|
72
|
+
options: RequestInit,
|
|
73
|
+
timeoutMs: number,
|
|
74
|
+
): Promise<Response> {
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch(url, {
|
|
80
|
+
...options,
|
|
81
|
+
signal: controller.signal,
|
|
82
|
+
});
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
return response;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
clearTimeout(timeout);
|
|
87
|
+
if ((err as Error).name === "AbortError") {
|
|
88
|
+
throw new Error("Request timeout");
|
|
89
|
+
}
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// MemoryRelay API Client (Full Suite)
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
export class MemoryRelayClient implements IMemoryRelayClient {
|
|
99
|
+
private debugLogger?: DebugLogger;
|
|
100
|
+
private statusReporter?: StatusReporter;
|
|
101
|
+
|
|
102
|
+
constructor(
|
|
103
|
+
private readonly apiKey: string,
|
|
104
|
+
private readonly agentId: string,
|
|
105
|
+
private readonly apiUrl: string = DEFAULT_API_URL,
|
|
106
|
+
debugLogger?: DebugLogger,
|
|
107
|
+
statusReporter?: StatusReporter,
|
|
108
|
+
) {
|
|
109
|
+
this.debugLogger = debugLogger;
|
|
110
|
+
this.statusReporter = statusReporter;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Extract tool name from API path
|
|
115
|
+
*/
|
|
116
|
+
private extractToolName(path: string): string {
|
|
117
|
+
// /v1/memories -> memory
|
|
118
|
+
// /v1/memories/batch -> memory_batch
|
|
119
|
+
// /v1/sessions/123/end -> session_end
|
|
120
|
+
const parts = path.split("/").filter(Boolean);
|
|
121
|
+
if (parts.length < 2) return "unknown";
|
|
122
|
+
|
|
123
|
+
let toolName = parts[1].replace(/s$/, ""); // Remove trailing 's'
|
|
124
|
+
|
|
125
|
+
// Check for specific endpoints
|
|
126
|
+
if (path.includes("/batch")) toolName += "_batch";
|
|
127
|
+
if (path.includes("/recall")) toolName += "_recall";
|
|
128
|
+
if (path.includes("/context")) toolName += "_context";
|
|
129
|
+
if (path.includes("/end")) toolName += "_end";
|
|
130
|
+
if (path.includes("/health")) return "memory_health";
|
|
131
|
+
|
|
132
|
+
return toolName;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Make HTTP request with retry logic and timeout
|
|
137
|
+
*/
|
|
138
|
+
private async request<T>(
|
|
139
|
+
method: string,
|
|
140
|
+
path: string,
|
|
141
|
+
body?: unknown,
|
|
142
|
+
retryCount = 0,
|
|
143
|
+
): Promise<T> {
|
|
144
|
+
const url = `${this.apiUrl}${path}`;
|
|
145
|
+
const startTime = Date.now();
|
|
146
|
+
const toolName = this.extractToolName(path);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const response = await fetchWithTimeout(
|
|
150
|
+
url,
|
|
151
|
+
{
|
|
152
|
+
method,
|
|
153
|
+
headers: {
|
|
154
|
+
"Content-Type": "application/json",
|
|
155
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
156
|
+
"User-Agent": "openclaw-memory-memoryrelay/0.16.1",
|
|
157
|
+
},
|
|
158
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
159
|
+
},
|
|
160
|
+
REQUEST_TIMEOUT_MS,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const duration = Date.now() - startTime;
|
|
164
|
+
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
const errorData = await response.json().catch(() => ({}));
|
|
167
|
+
const errorMsg = errorData.detail || errorData.message || "";
|
|
168
|
+
const error = new Error(
|
|
169
|
+
`MemoryRelay API error: ${response.status} ${response.statusText}` +
|
|
170
|
+
(errorMsg ? ` - ${errorMsg}` : ""),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Log error
|
|
174
|
+
if (this.debugLogger) {
|
|
175
|
+
this.debugLogger.log({
|
|
176
|
+
timestamp: new Date().toISOString(),
|
|
177
|
+
tool: toolName,
|
|
178
|
+
method,
|
|
179
|
+
path,
|
|
180
|
+
duration,
|
|
181
|
+
status: "error",
|
|
182
|
+
responseStatus: response.status,
|
|
183
|
+
error: error.message,
|
|
184
|
+
retries: retryCount,
|
|
185
|
+
requestBody: this.debugLogger && body ? body : undefined,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Track failure
|
|
190
|
+
if (this.statusReporter) {
|
|
191
|
+
this.statusReporter.recordFailure(toolName, `${response.status} ${errorMsg || response.statusText}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Retry on 5xx errors
|
|
195
|
+
if (response.status >= 500 && retryCount < MAX_RETRIES) {
|
|
196
|
+
const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
|
|
197
|
+
await sleep(delay);
|
|
198
|
+
return this.request<T>(method, path, body, retryCount + 1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const result = await response.json();
|
|
205
|
+
|
|
206
|
+
// Log success
|
|
207
|
+
if (this.debugLogger) {
|
|
208
|
+
this.debugLogger.log({
|
|
209
|
+
timestamp: new Date().toISOString(),
|
|
210
|
+
tool: toolName,
|
|
211
|
+
method,
|
|
212
|
+
path,
|
|
213
|
+
duration,
|
|
214
|
+
status: "success",
|
|
215
|
+
responseStatus: response.status,
|
|
216
|
+
retries: retryCount,
|
|
217
|
+
requestBody: this.debugLogger && body ? body : undefined,
|
|
218
|
+
responseBody: this.debugLogger && result ? result : undefined,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Track success
|
|
223
|
+
if (this.statusReporter) {
|
|
224
|
+
this.statusReporter.recordSuccess(toolName);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return result;
|
|
228
|
+
} catch (err) {
|
|
229
|
+
const duration = Date.now() - startTime;
|
|
230
|
+
|
|
231
|
+
// Log error
|
|
232
|
+
if (this.debugLogger) {
|
|
233
|
+
this.debugLogger.log({
|
|
234
|
+
timestamp: new Date().toISOString(),
|
|
235
|
+
tool: toolName,
|
|
236
|
+
method,
|
|
237
|
+
path,
|
|
238
|
+
duration,
|
|
239
|
+
status: "error",
|
|
240
|
+
error: String(err),
|
|
241
|
+
retries: retryCount,
|
|
242
|
+
requestBody: this.debugLogger && body ? body : undefined,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Track failure
|
|
247
|
+
if (this.statusReporter) {
|
|
248
|
+
this.statusReporter.recordFailure(toolName, String(err));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Retry on network errors
|
|
252
|
+
if (isRetryableError(err) && retryCount < MAX_RETRIES) {
|
|
253
|
+
const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
|
|
254
|
+
await sleep(delay);
|
|
255
|
+
return this.request<T>(method, path, body, retryCount + 1);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
throw err;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// --------------------------------------------------------------------------
|
|
263
|
+
// Memory operations
|
|
264
|
+
// --------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
async store(
|
|
267
|
+
content: string,
|
|
268
|
+
metadata?: Record<string, string>,
|
|
269
|
+
options?: {
|
|
270
|
+
deduplicate?: boolean;
|
|
271
|
+
dedup_threshold?: number;
|
|
272
|
+
project?: string;
|
|
273
|
+
importance?: number;
|
|
274
|
+
tier?: string;
|
|
275
|
+
scope?: string;
|
|
276
|
+
},
|
|
277
|
+
): Promise<Memory> {
|
|
278
|
+
// Extract session_id from metadata if present and move to top-level
|
|
279
|
+
const { session_id, ...cleanMetadata } = metadata || {};
|
|
280
|
+
|
|
281
|
+
const payload: any = {
|
|
282
|
+
content,
|
|
283
|
+
agent_id: this.agentId,
|
|
284
|
+
...options,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Only include metadata if there's something left after extracting session_id
|
|
288
|
+
if (Object.keys(cleanMetadata).length > 0) {
|
|
289
|
+
payload.metadata = cleanMetadata;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Add session_id as top-level parameter if provided
|
|
293
|
+
if (session_id) {
|
|
294
|
+
payload.session_id = session_id;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return this.request<Memory>("POST", "/v1/memories", payload);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async search(
|
|
301
|
+
query: string,
|
|
302
|
+
limit: number = 5,
|
|
303
|
+
threshold: number = 0.3,
|
|
304
|
+
opts?: {
|
|
305
|
+
include_confidential?: boolean;
|
|
306
|
+
include_archived?: boolean;
|
|
307
|
+
compress?: boolean;
|
|
308
|
+
max_context_tokens?: number;
|
|
309
|
+
project?: string;
|
|
310
|
+
tier?: string;
|
|
311
|
+
min_importance?: number;
|
|
312
|
+
scope?: string;
|
|
313
|
+
session_id?: string;
|
|
314
|
+
namespace?: string;
|
|
315
|
+
},
|
|
316
|
+
): Promise<SearchResult[]> {
|
|
317
|
+
const params = new URLSearchParams({
|
|
318
|
+
q: query,
|
|
319
|
+
limit: String(limit),
|
|
320
|
+
threshold: String(threshold),
|
|
321
|
+
});
|
|
322
|
+
if (opts?.scope) params.set("scope", opts.scope);
|
|
323
|
+
if (opts?.session_id) params.set("session_id", opts.session_id);
|
|
324
|
+
if (opts?.namespace) params.set("namespace", opts.namespace);
|
|
325
|
+
|
|
326
|
+
// Build POST body from remaining options (existing search contract)
|
|
327
|
+
const { scope, session_id, namespace, ...searchOptions } = opts || {};
|
|
328
|
+
|
|
329
|
+
const response = await this.request<{ data: SearchResult[] }>(
|
|
330
|
+
"POST",
|
|
331
|
+
`/v1/memories/search?${params.toString()}`,
|
|
332
|
+
{
|
|
333
|
+
query,
|
|
334
|
+
limit,
|
|
335
|
+
threshold,
|
|
336
|
+
agent_id: this.agentId,
|
|
337
|
+
...searchOptions,
|
|
338
|
+
},
|
|
339
|
+
);
|
|
340
|
+
return response.data || [];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async list(limit: number = 20, offset: number = 0, opts?: { scope?: string }): Promise<Memory[]> {
|
|
344
|
+
const cappedLimit = Math.min(limit, 100);
|
|
345
|
+
let path = `/v1/memories?limit=${cappedLimit}&offset=${offset}&agent_id=${encodeURIComponent(this.agentId)}`;
|
|
346
|
+
if (opts?.scope) path += `&scope=${encodeURIComponent(opts.scope)}`;
|
|
347
|
+
const response = await this.request<{ data: Memory[] }>(
|
|
348
|
+
"GET",
|
|
349
|
+
path,
|
|
350
|
+
);
|
|
351
|
+
return response.data || [];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async get(id: string): Promise<Memory> {
|
|
355
|
+
return this.request<Memory>("GET", `/v1/memories/${id}`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async update(id: string, content: string, metadata?: Record<string, string>): Promise<Memory> {
|
|
359
|
+
return this.request<Memory>("PUT", `/v1/memories/${id}`, {
|
|
360
|
+
content,
|
|
361
|
+
metadata,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async delete(id: string): Promise<void> {
|
|
366
|
+
await this.request<void>("DELETE", `/v1/memories/${id}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async batchStore(
|
|
370
|
+
memories: Array<{ content: string; metadata?: Record<string, string> }>,
|
|
371
|
+
): Promise<any> {
|
|
372
|
+
return this.request("POST", "/v1/memories/batch", {
|
|
373
|
+
memories,
|
|
374
|
+
agent_id: this.agentId,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async buildContext(
|
|
379
|
+
query: string,
|
|
380
|
+
limit?: number,
|
|
381
|
+
threshold?: number,
|
|
382
|
+
maxTokens?: number,
|
|
383
|
+
project?: string,
|
|
384
|
+
): Promise<any> {
|
|
385
|
+
return this.request("POST", "/v1/memories/context", {
|
|
386
|
+
query,
|
|
387
|
+
limit,
|
|
388
|
+
threshold,
|
|
389
|
+
max_tokens: maxTokens,
|
|
390
|
+
agent_id: this.agentId,
|
|
391
|
+
project,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async promote(memoryId: string, importance: number, tier?: string): Promise<any> {
|
|
396
|
+
return this.request("PUT", `/v1/memories/${memoryId}/importance`, {
|
|
397
|
+
importance,
|
|
398
|
+
tier,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// --------------------------------------------------------------------------
|
|
403
|
+
// V2 Async API Methods (v0.15.0)
|
|
404
|
+
// --------------------------------------------------------------------------
|
|
405
|
+
|
|
406
|
+
async storeAsync(
|
|
407
|
+
content: string,
|
|
408
|
+
metadata?: Record<string, string>,
|
|
409
|
+
project?: string,
|
|
410
|
+
importance?: number,
|
|
411
|
+
tier?: string,
|
|
412
|
+
webhook_url?: string,
|
|
413
|
+
): Promise<{ id: string; status: string; job_id: string; estimated_completion_seconds: number }> {
|
|
414
|
+
if (!content || content.length === 0 || content.length > 50000) {
|
|
415
|
+
throw new Error("Content must be between 1 and 50,000 characters");
|
|
416
|
+
}
|
|
417
|
+
const body: Record<string, unknown> = {
|
|
418
|
+
content,
|
|
419
|
+
agent_id: this.agentId,
|
|
420
|
+
};
|
|
421
|
+
if (metadata) body.metadata = metadata;
|
|
422
|
+
if (project) body.project = project;
|
|
423
|
+
if (importance != null) body.importance = importance;
|
|
424
|
+
if (tier) body.tier = tier;
|
|
425
|
+
if (webhook_url) body.webhook_url = webhook_url;
|
|
426
|
+
return this.request("POST", "/v2/memories", body);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async getMemoryStatus(memoryId: string): Promise<{
|
|
430
|
+
id: string;
|
|
431
|
+
status: "pending" | "processing" | "ready" | "failed";
|
|
432
|
+
created_at: string;
|
|
433
|
+
updated_at: string;
|
|
434
|
+
error?: string;
|
|
435
|
+
}> {
|
|
436
|
+
return this.request("GET", `/v2/memories/${memoryId}/status`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async buildContextV2(
|
|
440
|
+
query: string,
|
|
441
|
+
options?: {
|
|
442
|
+
maxMemories?: number;
|
|
443
|
+
maxTokens?: number;
|
|
444
|
+
aiEnhanced?: boolean;
|
|
445
|
+
searchMode?: "semantic" | "hybrid" | "keyword";
|
|
446
|
+
excludeMemoryIds?: string[];
|
|
447
|
+
},
|
|
448
|
+
): Promise<any> {
|
|
449
|
+
const body: Record<string, unknown> = {
|
|
450
|
+
query,
|
|
451
|
+
agent_id: this.agentId,
|
|
452
|
+
};
|
|
453
|
+
if (options?.maxMemories != null) body.max_memories = options.maxMemories;
|
|
454
|
+
if (options?.maxTokens != null) body.max_tokens = options.maxTokens;
|
|
455
|
+
if (options?.aiEnhanced != null) body.ai_enhanced = options.aiEnhanced;
|
|
456
|
+
if (options?.searchMode) body.search_mode = options.searchMode;
|
|
457
|
+
if (options?.excludeMemoryIds) body.exclude_memory_ids = options.excludeMemoryIds;
|
|
458
|
+
return this.request("POST", "/v2/context", body);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// --------------------------------------------------------------------------
|
|
462
|
+
// Entity operations
|
|
463
|
+
// --------------------------------------------------------------------------
|
|
464
|
+
|
|
465
|
+
async createEntity(
|
|
466
|
+
name: string,
|
|
467
|
+
type: string,
|
|
468
|
+
metadata?: Record<string, string>,
|
|
469
|
+
): Promise<any> {
|
|
470
|
+
return this.request("POST", "/v1/entities", {
|
|
471
|
+
name,
|
|
472
|
+
type,
|
|
473
|
+
metadata,
|
|
474
|
+
agent_id: this.agentId,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async linkEntity(
|
|
479
|
+
entityId: string,
|
|
480
|
+
memoryId: string,
|
|
481
|
+
relationship?: string,
|
|
482
|
+
): Promise<any> {
|
|
483
|
+
return this.request("POST", `/v1/entities/links`, {
|
|
484
|
+
entity_id: entityId,
|
|
485
|
+
memory_id: memoryId,
|
|
486
|
+
relationship,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async listEntities(limit: number = 20, offset: number = 0): Promise<any> {
|
|
491
|
+
return this.request("GET", `/v1/entities?limit=${limit}&offset=${offset}`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async entityGraph(
|
|
495
|
+
entityId: string,
|
|
496
|
+
depth: number = 2,
|
|
497
|
+
maxNeighbors: number = 10,
|
|
498
|
+
): Promise<any> {
|
|
499
|
+
return this.request(
|
|
500
|
+
"GET",
|
|
501
|
+
`/v1/entities/${entityId}/neighborhood?depth=${depth}&max_neighbors=${maxNeighbors}`,
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// --------------------------------------------------------------------------
|
|
506
|
+
// Agent operations
|
|
507
|
+
// --------------------------------------------------------------------------
|
|
508
|
+
|
|
509
|
+
async listAgents(limit: number = 20): Promise<any> {
|
|
510
|
+
return this.request("GET", `/v1/agents?limit=${limit}`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async createAgent(name: string, description?: string): Promise<any> {
|
|
514
|
+
return this.request("POST", "/v1/agents", { name, description });
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async getAgent(id: string): Promise<any> {
|
|
518
|
+
return this.request("GET", `/v1/agents/${id}`);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// --------------------------------------------------------------------------
|
|
522
|
+
// Session operations
|
|
523
|
+
// --------------------------------------------------------------------------
|
|
524
|
+
|
|
525
|
+
async startSession(
|
|
526
|
+
title?: string,
|
|
527
|
+
project?: string,
|
|
528
|
+
metadata?: Record<string, string>,
|
|
529
|
+
): Promise<any> {
|
|
530
|
+
return this.request("POST", "/v1/sessions", {
|
|
531
|
+
title,
|
|
532
|
+
project,
|
|
533
|
+
metadata,
|
|
534
|
+
agent_id: this.agentId,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async getOrCreateSession(
|
|
539
|
+
external_id: string,
|
|
540
|
+
agent_id?: string,
|
|
541
|
+
title?: string,
|
|
542
|
+
project?: string,
|
|
543
|
+
metadata?: Record<string, string>,
|
|
544
|
+
): Promise<any> {
|
|
545
|
+
return this.request("POST", "/v1/sessions/get-or-create", {
|
|
546
|
+
external_id,
|
|
547
|
+
agent_id: agent_id || this.agentId,
|
|
548
|
+
title,
|
|
549
|
+
project,
|
|
550
|
+
metadata,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async endSession(id: string, summary?: string): Promise<any> {
|
|
555
|
+
return this.request("PUT", `/v1/sessions/${id}/end`, { summary });
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async getSession(id: string): Promise<any> {
|
|
559
|
+
return this.request("GET", `/v1/sessions/${id}`);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async listSessions(
|
|
563
|
+
limit: number = 20,
|
|
564
|
+
project?: string,
|
|
565
|
+
status?: string,
|
|
566
|
+
): Promise<any> {
|
|
567
|
+
let path = `/v1/sessions?limit=${limit}`;
|
|
568
|
+
if (project) path += `&project=${encodeURIComponent(project)}`;
|
|
569
|
+
if (status) path += `&status=${encodeURIComponent(status)}`;
|
|
570
|
+
return this.request("GET", path);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// --------------------------------------------------------------------------
|
|
574
|
+
// Decision operations
|
|
575
|
+
// --------------------------------------------------------------------------
|
|
576
|
+
|
|
577
|
+
async recordDecision(
|
|
578
|
+
title: string,
|
|
579
|
+
rationale: string,
|
|
580
|
+
alternatives?: string,
|
|
581
|
+
project?: string,
|
|
582
|
+
tags?: string[],
|
|
583
|
+
status?: string,
|
|
584
|
+
metadata?: Record<string, string>,
|
|
585
|
+
): Promise<any> {
|
|
586
|
+
return this.request("POST", "/v1/decisions", {
|
|
587
|
+
title,
|
|
588
|
+
rationale,
|
|
589
|
+
alternatives,
|
|
590
|
+
project_slug: project,
|
|
591
|
+
tags,
|
|
592
|
+
status,
|
|
593
|
+
metadata,
|
|
594
|
+
agent_id: this.agentId,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async listDecisions(
|
|
599
|
+
limit: number = 20,
|
|
600
|
+
project?: string,
|
|
601
|
+
status?: string,
|
|
602
|
+
tags?: string,
|
|
603
|
+
): Promise<any> {
|
|
604
|
+
let path = `/v1/decisions?limit=${limit}`;
|
|
605
|
+
if (project) path += `&project=${encodeURIComponent(project)}`;
|
|
606
|
+
if (status) path += `&status=${encodeURIComponent(status)}`;
|
|
607
|
+
if (tags) path += `&tags=${encodeURIComponent(tags)}`;
|
|
608
|
+
return this.request("GET", path);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async supersedeDecision(
|
|
612
|
+
id: string,
|
|
613
|
+
title: string,
|
|
614
|
+
rationale: string,
|
|
615
|
+
alternatives?: string,
|
|
616
|
+
tags?: string[],
|
|
617
|
+
): Promise<any> {
|
|
618
|
+
return this.request("POST", `/v1/decisions/${id}/supersede`, {
|
|
619
|
+
title,
|
|
620
|
+
rationale,
|
|
621
|
+
alternatives,
|
|
622
|
+
tags,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async checkDecisions(
|
|
627
|
+
query: string,
|
|
628
|
+
project?: string,
|
|
629
|
+
limit?: number,
|
|
630
|
+
threshold?: number,
|
|
631
|
+
includeSuperseded?: boolean,
|
|
632
|
+
): Promise<any> {
|
|
633
|
+
const params = new URLSearchParams();
|
|
634
|
+
params.set("query", query);
|
|
635
|
+
if (project) params.set("project", project);
|
|
636
|
+
if (limit !== undefined) params.set("limit", String(limit));
|
|
637
|
+
if (threshold !== undefined) params.set("threshold", String(threshold));
|
|
638
|
+
if (includeSuperseded) params.set("include_superseded", "true");
|
|
639
|
+
return this.request("GET", `/v1/decisions/check?${params.toString()}`);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// --------------------------------------------------------------------------
|
|
643
|
+
// Pattern operations
|
|
644
|
+
// --------------------------------------------------------------------------
|
|
645
|
+
|
|
646
|
+
async createPattern(
|
|
647
|
+
title: string,
|
|
648
|
+
description: string,
|
|
649
|
+
category?: string,
|
|
650
|
+
exampleCode?: string,
|
|
651
|
+
scope?: string,
|
|
652
|
+
tags?: string[],
|
|
653
|
+
sourceProject?: string,
|
|
654
|
+
): Promise<any> {
|
|
655
|
+
return this.request("POST", "/v1/patterns", {
|
|
656
|
+
title,
|
|
657
|
+
description,
|
|
658
|
+
category,
|
|
659
|
+
example_code: exampleCode,
|
|
660
|
+
scope,
|
|
661
|
+
tags,
|
|
662
|
+
source_project: sourceProject,
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async searchPatterns(
|
|
667
|
+
query: string,
|
|
668
|
+
category?: string,
|
|
669
|
+
project?: string,
|
|
670
|
+
limit?: number,
|
|
671
|
+
threshold?: number,
|
|
672
|
+
): Promise<any> {
|
|
673
|
+
const params = new URLSearchParams();
|
|
674
|
+
params.set("query", query);
|
|
675
|
+
if (category) params.set("category", category);
|
|
676
|
+
if (project) params.set("project", project);
|
|
677
|
+
if (limit !== undefined) params.set("limit", String(limit));
|
|
678
|
+
if (threshold !== undefined) params.set("threshold", String(threshold));
|
|
679
|
+
return this.request("GET", `/v1/patterns/search?${params.toString()}`);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async adoptPattern(id: string, project: string): Promise<any> {
|
|
683
|
+
return this.request("POST", `/v1/patterns/${id}/adopt`, { project });
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async suggestPatterns(project: string, limit?: number): Promise<any> {
|
|
687
|
+
let path = `/v1/patterns/suggest?project=${encodeURIComponent(project)}`;
|
|
688
|
+
if (limit) path += `&limit=${limit}`;
|
|
689
|
+
return this.request("GET", path);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// --------------------------------------------------------------------------
|
|
693
|
+
// Project operations
|
|
694
|
+
// --------------------------------------------------------------------------
|
|
695
|
+
|
|
696
|
+
async registerProject(
|
|
697
|
+
slug: string,
|
|
698
|
+
name: string,
|
|
699
|
+
description?: string,
|
|
700
|
+
stack?: Record<string, unknown>,
|
|
701
|
+
repoUrl?: string,
|
|
702
|
+
): Promise<any> {
|
|
703
|
+
return this.request("POST", "/v1/projects", {
|
|
704
|
+
slug,
|
|
705
|
+
name,
|
|
706
|
+
description,
|
|
707
|
+
stack,
|
|
708
|
+
repo_url: repoUrl,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async listProjects(limit: number = 20): Promise<any> {
|
|
713
|
+
return this.request("GET", `/v1/projects?limit=${limit}`);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async getProject(slug: string): Promise<any> {
|
|
717
|
+
return this.request("GET", `/v1/projects/${encodeURIComponent(slug)}`);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
async addProjectRelationship(
|
|
721
|
+
from: string,
|
|
722
|
+
to: string,
|
|
723
|
+
type: string,
|
|
724
|
+
metadata?: Record<string, unknown>,
|
|
725
|
+
): Promise<any> {
|
|
726
|
+
return this.request("POST", `/v1/projects/${encodeURIComponent(from)}/relationships`, {
|
|
727
|
+
target_project: to,
|
|
728
|
+
relationship_type: type,
|
|
729
|
+
metadata,
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
async getProjectDependencies(project: string): Promise<any> {
|
|
734
|
+
return this.request(
|
|
735
|
+
"GET",
|
|
736
|
+
`/v1/projects/${encodeURIComponent(project)}/dependencies`,
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
async getProjectDependents(project: string): Promise<any> {
|
|
741
|
+
return this.request(
|
|
742
|
+
"GET",
|
|
743
|
+
`/v1/projects/${encodeURIComponent(project)}/dependents`,
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
async getProjectRelated(project: string): Promise<any> {
|
|
748
|
+
return this.request(
|
|
749
|
+
"GET",
|
|
750
|
+
`/v1/projects/${encodeURIComponent(project)}/related`,
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
async projectImpact(project: string, changeDescription: string): Promise<any> {
|
|
755
|
+
return this.request(
|
|
756
|
+
"POST",
|
|
757
|
+
`/v1/projects/impact-analysis`,
|
|
758
|
+
{ project, change_description: changeDescription },
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
async getSharedPatterns(projectA: string, projectB: string): Promise<any> {
|
|
763
|
+
const params = new URLSearchParams();
|
|
764
|
+
params.set("a", projectA);
|
|
765
|
+
params.set("b", projectB);
|
|
766
|
+
return this.request(
|
|
767
|
+
"GET",
|
|
768
|
+
`/v1/projects/shared-patterns?${params.toString()}`,
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
async getProjectContext(project: string): Promise<any> {
|
|
773
|
+
return this.request(
|
|
774
|
+
"GET",
|
|
775
|
+
`/v1/projects/${encodeURIComponent(project)}/context`,
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// --------------------------------------------------------------------------
|
|
780
|
+
// Health & stats
|
|
781
|
+
// --------------------------------------------------------------------------
|
|
782
|
+
|
|
783
|
+
async health(): Promise<{ status: string }> {
|
|
784
|
+
return this.request<{ status: string }>("GET", "/v1/health");
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
async stats(): Promise<Stats> {
|
|
788
|
+
const response = await this.request<{ data: Stats }>(
|
|
789
|
+
"GET",
|
|
790
|
+
`/v1/agents/${encodeURIComponent(this.agentId)}/stats`,
|
|
791
|
+
);
|
|
792
|
+
return {
|
|
793
|
+
total_memories: response.data?.total_memories ?? 0,
|
|
794
|
+
last_updated: response.data?.last_updated,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Export all memories as JSON
|
|
800
|
+
*/
|
|
801
|
+
async export(): Promise<Memory[]> {
|
|
802
|
+
const allMemories: Memory[] = [];
|
|
803
|
+
let offset = 0;
|
|
804
|
+
const limit = 100;
|
|
805
|
+
|
|
806
|
+
while (true) {
|
|
807
|
+
const batch = await this.list(limit, offset);
|
|
808
|
+
if (batch.length === 0) break;
|
|
809
|
+
allMemories.push(...batch);
|
|
810
|
+
offset += limit;
|
|
811
|
+
if (batch.length < limit) break;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
return allMemories;
|
|
815
|
+
}
|
|
816
|
+
}
|