@vectorize-io/hindsight-openclaw 0.5.1 → 0.6.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 +147 -18
- package/dist/backfill-lib.d.ts +63 -0
- package/dist/backfill-lib.js +201 -0
- package/dist/backfill.d.ts +22 -0
- package/dist/backfill.js +473 -0
- package/dist/index.d.ts +49 -2
- package/dist/index.js +612 -344
- package/dist/retain-queue.d.ts +54 -0
- package/dist/retain-queue.js +105 -0
- package/dist/session-patterns.d.ts +10 -0
- package/dist/session-patterns.js +21 -0
- package/dist/setup-lib.d.ts +80 -0
- package/dist/setup-lib.js +134 -0
- package/dist/setup.d.ts +34 -0
- package/dist/setup.js +425 -0
- package/dist/types.d.ts +40 -40
- package/openclaw.plugin.json +110 -10
- package/package.json +13 -5
- package/dist/client.d.ts +0 -34
- package/dist/client.js +0 -215
- package/dist/embed-manager.d.ts +0 -27
- package/dist/embed-manager.js +0 -210
package/openclaw.plugin.json
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
"id": "hindsight-openclaw",
|
|
3
3
|
"name": "Hindsight Memory",
|
|
4
4
|
"kind": "memory",
|
|
5
|
+
"configContracts": {
|
|
6
|
+
"secretInputs": {
|
|
7
|
+
"paths": [
|
|
8
|
+
{ "path": "llmApiKey", "expected": "string" },
|
|
9
|
+
{ "path": "hindsightApiToken", "expected": "string" }
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
},
|
|
5
13
|
"configSchema": {
|
|
6
14
|
"type": "object",
|
|
7
15
|
"properties": {
|
|
@@ -27,7 +35,7 @@
|
|
|
27
35
|
},
|
|
28
36
|
"llmProvider": {
|
|
29
37
|
"type": "string",
|
|
30
|
-
"description": "LLM provider for Hindsight memory (e.g. 'openai', 'anthropic', 'gemini', 'groq', 'ollama', 'openai-codex', 'claude-code').
|
|
38
|
+
"description": "LLM provider for Hindsight memory (e.g. 'openai', 'anthropic', 'gemini', 'groq', 'ollama', 'openai-codex', 'claude-code').",
|
|
31
39
|
"enum": [
|
|
32
40
|
"openai",
|
|
33
41
|
"anthropic",
|
|
@@ -42,9 +50,13 @@
|
|
|
42
50
|
"type": "string",
|
|
43
51
|
"description": "LLM model to use (e.g. 'gpt-4o-mini', 'claude-3-5-haiku-20241022'). Used with llmProvider."
|
|
44
52
|
},
|
|
45
|
-
"
|
|
53
|
+
"llmApiKey": {
|
|
54
|
+
"type": ["string", "object"],
|
|
55
|
+
"description": "API key for the LLM provider used by the Hindsight memory daemon. Set via 'openclaw config set ... --ref-source env --ref-id OPENAI_API_KEY' to reference an env var without storing plaintext."
|
|
56
|
+
},
|
|
57
|
+
"llmBaseUrl": {
|
|
46
58
|
"type": "string",
|
|
47
|
-
"description": "
|
|
59
|
+
"description": "Optional base URL override for OpenAI-compatible providers (e.g. 'https://openrouter.ai/api/v1')."
|
|
48
60
|
},
|
|
49
61
|
"embedPackagePath": {
|
|
50
62
|
"type": "string",
|
|
@@ -60,7 +72,7 @@
|
|
|
60
72
|
"description": "External Hindsight API URL (e.g. 'https://mcp.hindsight.devcraft.team'). When set, skips local daemon and connects directly to this API."
|
|
61
73
|
},
|
|
62
74
|
"hindsightApiToken": {
|
|
63
|
-
"type": "string",
|
|
75
|
+
"type": ["string", "object"],
|
|
64
76
|
"description": "API token for external Hindsight API authentication. Required if the external API has authentication enabled."
|
|
65
77
|
},
|
|
66
78
|
"dynamicBankId": {
|
|
@@ -68,10 +80,26 @@
|
|
|
68
80
|
"description": "Enable per-user memory banks. When true, memories are isolated by user per channel (e.g., slack-U123, telegram-456789). When false, all users share a single 'openclaw' bank.",
|
|
69
81
|
"default": true
|
|
70
82
|
},
|
|
83
|
+
"bankId": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"description": "Static bank ID used when dynamicBankId is false."
|
|
86
|
+
},
|
|
71
87
|
"bankIdPrefix": {
|
|
72
88
|
"type": "string",
|
|
73
89
|
"description": "Optional prefix for bank IDs (e.g., 'prod' results in 'prod-slack-U123'). Useful for separating environments."
|
|
74
90
|
},
|
|
91
|
+
"retainTags": {
|
|
92
|
+
"type": "array",
|
|
93
|
+
"items": {
|
|
94
|
+
"type": "string"
|
|
95
|
+
},
|
|
96
|
+
"description": "Tags applied to every retained document (e.g. ['source_system:openclaw', 'agent:agentname'])."
|
|
97
|
+
},
|
|
98
|
+
"retainSource": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "Source value written into retained document metadata. Defaults to 'openclaw'.",
|
|
101
|
+
"default": "openclaw"
|
|
102
|
+
},
|
|
75
103
|
"autoRecall": {
|
|
76
104
|
"type": "boolean",
|
|
77
105
|
"description": "Automatically recall memories on every prompt and inject them as context. Set to false when agent has its own recall tool.",
|
|
@@ -82,7 +110,8 @@
|
|
|
82
110
|
"items": {
|
|
83
111
|
"type": "string"
|
|
84
112
|
},
|
|
85
|
-
"description": "Message providers to exclude from recall and retain (e.g. ['telegram', 'discord'])"
|
|
113
|
+
"description": "Message providers to exclude from recall and retain (e.g. ['heartbeat', 'telegram', 'discord'])",
|
|
114
|
+
"default": ["heartbeat"]
|
|
86
115
|
},
|
|
87
116
|
"dynamicBankGranularity": {
|
|
88
117
|
"type": "array",
|
|
@@ -215,6 +244,35 @@
|
|
|
215
244
|
"type": "number",
|
|
216
245
|
"description": "Interval in ms to batch retain/recall log summaries. 0 = log every event individually. Default: 300000 (5 min).",
|
|
217
246
|
"default": 300000
|
|
247
|
+
},
|
|
248
|
+
"retainQueuePath": {
|
|
249
|
+
"type": "string",
|
|
250
|
+
"description": "Path to JSONL file for buffering failed retains (external API mode only). Default: ~/.openclaw/data/hindsight-retain-queue.jsonl"
|
|
251
|
+
},
|
|
252
|
+
"retainQueueMaxAgeMs": {
|
|
253
|
+
"type": "number",
|
|
254
|
+
"description": "Max age in ms for queued retain items. -1 = keep forever.",
|
|
255
|
+
"default": -1
|
|
256
|
+
},
|
|
257
|
+
"retainQueueFlushIntervalMs": {
|
|
258
|
+
"type": "number",
|
|
259
|
+
"description": "How often to attempt flushing queued retains in ms. Default: 60000 (1 min).",
|
|
260
|
+
"default": 60000
|
|
261
|
+
},
|
|
262
|
+
"ignoreSessionPatterns": {
|
|
263
|
+
"type": "array",
|
|
264
|
+
"items": { "type": "string" },
|
|
265
|
+
"description": "Session key glob patterns to skip entirely (no recall, no retain). E.g. [\"agent:main:**\", \"agent:*:cron:**\"]. * matches non-colon chars, ** matches anything."
|
|
266
|
+
},
|
|
267
|
+
"statelessSessionPatterns": {
|
|
268
|
+
"type": "array",
|
|
269
|
+
"items": { "type": "string" },
|
|
270
|
+
"description": "Session key glob patterns for read-only sessions: retain is always skipped, recall is skipped when skipStatelessSessions is true. E.g. [\"agent:*:subagent:**\", \"agent:*:heartbeat:**\"]."
|
|
271
|
+
},
|
|
272
|
+
"skipStatelessSessions": {
|
|
273
|
+
"type": "boolean",
|
|
274
|
+
"description": "When true (default), sessions matching statelessSessionPatterns also skip recall. When false, they can recall but never retain.",
|
|
275
|
+
"default": true
|
|
218
276
|
}
|
|
219
277
|
},
|
|
220
278
|
"additionalProperties": false
|
|
@@ -244,9 +302,14 @@
|
|
|
244
302
|
"label": "LLM Model",
|
|
245
303
|
"placeholder": "e.g. gpt-4o-mini, claude-3-5-haiku-20241022"
|
|
246
304
|
},
|
|
247
|
-
"
|
|
248
|
-
"label": "API Key
|
|
249
|
-
"placeholder": "
|
|
305
|
+
"llmApiKey": {
|
|
306
|
+
"label": "LLM API Key",
|
|
307
|
+
"placeholder": "API key for the chosen LLM provider",
|
|
308
|
+
"sensitive": true
|
|
309
|
+
},
|
|
310
|
+
"llmBaseUrl": {
|
|
311
|
+
"label": "LLM Base URL",
|
|
312
|
+
"placeholder": "e.g. https://openrouter.ai/api/v1 (optional)"
|
|
250
313
|
},
|
|
251
314
|
"embedPackagePath": {
|
|
252
315
|
"label": "Local Package Path (Dev)",
|
|
@@ -262,23 +325,36 @@
|
|
|
262
325
|
},
|
|
263
326
|
"hindsightApiToken": {
|
|
264
327
|
"label": "External API Token",
|
|
265
|
-
"placeholder": "API token if external API requires authentication"
|
|
328
|
+
"placeholder": "API token if external API requires authentication",
|
|
329
|
+
"sensitive": true
|
|
266
330
|
},
|
|
267
331
|
"dynamicBankId": {
|
|
268
332
|
"label": "Dynamic Bank IDs",
|
|
269
333
|
"placeholder": "true (isolate memories per channel)"
|
|
270
334
|
},
|
|
335
|
+
"bankId": {
|
|
336
|
+
"label": "Static Bank ID",
|
|
337
|
+
"placeholder": "e.g. openclaw, shared-bank (used when dynamicBankId is false)"
|
|
338
|
+
},
|
|
271
339
|
"bankIdPrefix": {
|
|
272
340
|
"label": "Bank ID Prefix",
|
|
273
341
|
"placeholder": "e.g., prod, staging (optional)"
|
|
274
342
|
},
|
|
343
|
+
"retainTags": {
|
|
344
|
+
"label": "Retain Tags",
|
|
345
|
+
"placeholder": "e.g. ['source_system:openclaw', 'agent:agentname']"
|
|
346
|
+
},
|
|
347
|
+
"retainSource": {
|
|
348
|
+
"label": "Retain Source",
|
|
349
|
+
"placeholder": "openclaw"
|
|
350
|
+
},
|
|
275
351
|
"autoRecall": {
|
|
276
352
|
"label": "Auto-Recall",
|
|
277
353
|
"placeholder": "true (inject memories on every prompt)"
|
|
278
354
|
},
|
|
279
355
|
"excludeProviders": {
|
|
280
356
|
"label": "Excluded Providers",
|
|
281
|
-
"placeholder": "e.g. telegram, discord"
|
|
357
|
+
"placeholder": "e.g. heartbeat, telegram, discord"
|
|
282
358
|
},
|
|
283
359
|
"dynamicBankGranularity": {
|
|
284
360
|
"label": "Bank Granularity",
|
|
@@ -349,6 +425,30 @@
|
|
|
349
425
|
"logSummaryIntervalMs": {
|
|
350
426
|
"label": "Log Summary Interval (ms)",
|
|
351
427
|
"placeholder": "300000"
|
|
428
|
+
},
|
|
429
|
+
"retainQueuePath": {
|
|
430
|
+
"label": "Retain Queue File Path",
|
|
431
|
+
"placeholder": "~/.openclaw/data/hindsight-retain-queue.jsonl"
|
|
432
|
+
},
|
|
433
|
+
"retainQueueMaxAgeMs": {
|
|
434
|
+
"label": "Retain Queue Max Age (ms)",
|
|
435
|
+
"placeholder": "-1 (forever)"
|
|
436
|
+
},
|
|
437
|
+
"retainQueueFlushIntervalMs": {
|
|
438
|
+
"label": "Retain Queue Flush Interval (ms)",
|
|
439
|
+
"placeholder": "60000"
|
|
440
|
+
},
|
|
441
|
+
"ignoreSessionPatterns": {
|
|
442
|
+
"label": "Ignore Session Patterns",
|
|
443
|
+
"placeholder": "e.g. [\"agent:main:**\", \"agent:*:cron:**\"]"
|
|
444
|
+
},
|
|
445
|
+
"statelessSessionPatterns": {
|
|
446
|
+
"label": "Stateless Session Patterns",
|
|
447
|
+
"placeholder": "e.g. [\"agent:*:subagent:**\", \"agent:*:heartbeat:**\"]"
|
|
448
|
+
},
|
|
449
|
+
"skipStatelessSessions": {
|
|
450
|
+
"label": "Skip Stateless Sessions",
|
|
451
|
+
"placeholder": "true (default)"
|
|
352
452
|
}
|
|
353
453
|
}
|
|
354
454
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vectorize-io/hindsight-openclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Hindsight memory plugin for OpenClaw - biomimetic long-term memory with fact extraction",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"hindsight-openclaw-backfill": "dist/backfill.js",
|
|
9
|
+
"hindsight-openclaw-setup": "dist/setup.js"
|
|
10
|
+
},
|
|
7
11
|
"type": "module",
|
|
8
12
|
"openclaw": {
|
|
9
13
|
"extensions": [
|
|
@@ -34,25 +38,29 @@
|
|
|
34
38
|
"build": "tsc",
|
|
35
39
|
"dev": "tsc --watch",
|
|
36
40
|
"clean": "rm -rf dist",
|
|
41
|
+
"backfill:help": "node dist/backfill.js --help",
|
|
37
42
|
"test": "vitest run src",
|
|
38
43
|
"test:watch": "vitest src",
|
|
39
44
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
40
45
|
"prepublishOnly": "npm run clean && npm run build"
|
|
41
46
|
},
|
|
42
47
|
"dependencies": {
|
|
43
|
-
"
|
|
48
|
+
"@clack/prompts": "^1.2.0",
|
|
49
|
+
"@vectorize-io/hindsight-client": "^0.5.0",
|
|
50
|
+
"@vectorize-io/hindsight-all": "^0.1.0"
|
|
44
51
|
},
|
|
45
52
|
"devDependencies": {
|
|
46
53
|
"@types/node": "^20.0.0",
|
|
47
|
-
"@vitest/ui": "^4.
|
|
54
|
+
"@vitest/ui": "^4.1.2",
|
|
48
55
|
"typescript": "^5.3.0",
|
|
49
|
-
"vitest": "^4.
|
|
56
|
+
"vitest": "^4.1.2"
|
|
50
57
|
},
|
|
51
58
|
"engines": {
|
|
52
59
|
"node": ">=22"
|
|
53
60
|
},
|
|
54
61
|
"overrides": {
|
|
55
62
|
"rollup": "^4.59.0",
|
|
56
|
-
"picomatch": ">=2.3.2 <3.0.0 || >=4.0.4"
|
|
63
|
+
"picomatch": ">=2.3.2 <3.0.0 || >=4.0.4",
|
|
64
|
+
"vite": ">=8.0.5"
|
|
57
65
|
}
|
|
58
66
|
}
|
package/dist/client.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { RetainRequest, RetainResponse, RecallRequest, RecallResponse } from './types.js';
|
|
2
|
-
export interface HindsightClientOptions {
|
|
3
|
-
llmModel?: string;
|
|
4
|
-
embedVersion?: string;
|
|
5
|
-
embedPackagePath?: string;
|
|
6
|
-
apiUrl?: string;
|
|
7
|
-
apiToken?: string;
|
|
8
|
-
}
|
|
9
|
-
export declare class HindsightClient {
|
|
10
|
-
private bankId;
|
|
11
|
-
private llmModel?;
|
|
12
|
-
private embedVersion;
|
|
13
|
-
private embedPackagePath?;
|
|
14
|
-
private apiUrl?;
|
|
15
|
-
private apiToken?;
|
|
16
|
-
constructor(opts: HindsightClientOptions);
|
|
17
|
-
private get httpMode();
|
|
18
|
-
/**
|
|
19
|
-
* Get the command and base args to run hindsight-embed.
|
|
20
|
-
* Returns [command, ...baseArgs] for use with execFile/spawn (no shell).
|
|
21
|
-
*/
|
|
22
|
-
private getEmbedCommand;
|
|
23
|
-
private httpHeaders;
|
|
24
|
-
setBankId(bankId: string): void;
|
|
25
|
-
setBankMission(mission: string): Promise<void>;
|
|
26
|
-
private setBankMissionHttp;
|
|
27
|
-
private setBankMissionSubprocess;
|
|
28
|
-
retain(request: RetainRequest): Promise<RetainResponse>;
|
|
29
|
-
private retainHttp;
|
|
30
|
-
private retainSubprocess;
|
|
31
|
-
recall(request: RecallRequest, timeoutMs?: number): Promise<RecallResponse>;
|
|
32
|
-
private recallHttp;
|
|
33
|
-
private recallSubprocess;
|
|
34
|
-
}
|
package/dist/client.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'child_process';
|
|
2
|
-
import { promisify } from 'util';
|
|
3
|
-
import { writeFile, mkdir, rm } from 'fs/promises';
|
|
4
|
-
import { tmpdir } from 'os';
|
|
5
|
-
import { join } from 'path';
|
|
6
|
-
import { randomBytes } from 'crypto';
|
|
7
|
-
import * as log from './logger.js';
|
|
8
|
-
const execFileAsync = promisify(execFile);
|
|
9
|
-
const MAX_BUFFER = 5 * 1024 * 1024; // 5 MB — large transcripts can exceed default 1 MB
|
|
10
|
-
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
11
|
-
/** Strip null bytes from strings — Node 22 rejects them in execFile() args */
|
|
12
|
-
const sanitize = (s) => s.replace(/\0/g, '');
|
|
13
|
-
/**
|
|
14
|
-
* Sanitize a string for use as a cross-platform filename.
|
|
15
|
-
* Replaces characters illegal on Windows or Unix with underscores.
|
|
16
|
-
*/
|
|
17
|
-
function sanitizeFilename(name) {
|
|
18
|
-
// Replace characters illegal on Windows (\/:*?"<>|) and control chars
|
|
19
|
-
return name.replace(/[\\/:*?"<>|\x00-\x1f]/g, '_').slice(0, 200) || 'content';
|
|
20
|
-
}
|
|
21
|
-
export class HindsightClient {
|
|
22
|
-
bankId = 'default';
|
|
23
|
-
llmModel;
|
|
24
|
-
embedVersion;
|
|
25
|
-
embedPackagePath;
|
|
26
|
-
apiUrl;
|
|
27
|
-
apiToken;
|
|
28
|
-
constructor(opts) {
|
|
29
|
-
this.llmModel = opts.llmModel;
|
|
30
|
-
this.embedVersion = opts.embedVersion || 'latest';
|
|
31
|
-
this.embedPackagePath = opts.embedPackagePath;
|
|
32
|
-
this.apiUrl = opts.apiUrl?.replace(/\/$/, ''); // strip trailing slash
|
|
33
|
-
this.apiToken = opts.apiToken;
|
|
34
|
-
}
|
|
35
|
-
get httpMode() {
|
|
36
|
-
return !!this.apiUrl;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Get the command and base args to run hindsight-embed.
|
|
40
|
-
* Returns [command, ...baseArgs] for use with execFile/spawn (no shell).
|
|
41
|
-
*/
|
|
42
|
-
getEmbedCommand() {
|
|
43
|
-
if (this.embedPackagePath) {
|
|
44
|
-
return ['uv', 'run', '--directory', this.embedPackagePath, 'hindsight-embed'];
|
|
45
|
-
}
|
|
46
|
-
const embedPackage = this.embedVersion ? `hindsight-embed@${this.embedVersion}` : 'hindsight-embed@latest';
|
|
47
|
-
return ['uvx', embedPackage];
|
|
48
|
-
}
|
|
49
|
-
httpHeaders() {
|
|
50
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
51
|
-
if (this.apiToken) {
|
|
52
|
-
headers['Authorization'] = `Bearer ${this.apiToken}`;
|
|
53
|
-
}
|
|
54
|
-
return headers;
|
|
55
|
-
}
|
|
56
|
-
setBankId(bankId) {
|
|
57
|
-
this.bankId = bankId;
|
|
58
|
-
}
|
|
59
|
-
// --- setBankMission ---
|
|
60
|
-
async setBankMission(mission) {
|
|
61
|
-
if (!mission || mission.trim().length === 0) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (this.httpMode) {
|
|
65
|
-
return this.setBankMissionHttp(mission);
|
|
66
|
-
}
|
|
67
|
-
return this.setBankMissionSubprocess(mission);
|
|
68
|
-
}
|
|
69
|
-
async setBankMissionHttp(mission) {
|
|
70
|
-
try {
|
|
71
|
-
const url = `${this.apiUrl}/v1/default/banks/${encodeURIComponent(this.bankId)}`;
|
|
72
|
-
const res = await fetch(url, {
|
|
73
|
-
method: 'PUT',
|
|
74
|
-
headers: this.httpHeaders(),
|
|
75
|
-
body: JSON.stringify({ mission }),
|
|
76
|
-
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS),
|
|
77
|
-
});
|
|
78
|
-
if (!res.ok) {
|
|
79
|
-
const body = await res.text().catch(() => '');
|
|
80
|
-
throw new Error(`HTTP ${res.status}: ${body}`);
|
|
81
|
-
}
|
|
82
|
-
log.verbose('bank mission set via HTTP');
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
log.warn(`could not set bank mission (bank may not exist yet): ${error}`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
async setBankMissionSubprocess(mission) {
|
|
89
|
-
const [cmd, ...baseArgs] = this.getEmbedCommand();
|
|
90
|
-
const args = [...baseArgs, '--profile', 'openclaw', 'bank', 'mission', this.bankId, sanitize(mission)];
|
|
91
|
-
try {
|
|
92
|
-
const { stdout } = await execFileAsync(cmd, args, { maxBuffer: MAX_BUFFER });
|
|
93
|
-
log.verbose(`bank mission set: ${stdout.trim()}`);
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
// Don't fail if mission set fails - bank might not exist yet, will be created on first retain
|
|
97
|
-
log.warn(`could not set bank mission (bank may not exist yet): ${error}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// --- retain ---
|
|
101
|
-
async retain(request) {
|
|
102
|
-
if (this.httpMode) {
|
|
103
|
-
return this.retainHttp(request);
|
|
104
|
-
}
|
|
105
|
-
return this.retainSubprocess(request);
|
|
106
|
-
}
|
|
107
|
-
async retainHttp(request) {
|
|
108
|
-
const url = `${this.apiUrl}/v1/default/banks/${encodeURIComponent(this.bankId)}/memories`;
|
|
109
|
-
const body = {
|
|
110
|
-
items: [{
|
|
111
|
-
content: request.content,
|
|
112
|
-
document_id: request.document_id || 'conversation',
|
|
113
|
-
metadata: request.metadata,
|
|
114
|
-
}],
|
|
115
|
-
async: true,
|
|
116
|
-
};
|
|
117
|
-
const res = await fetch(url, {
|
|
118
|
-
method: 'POST',
|
|
119
|
-
headers: this.httpHeaders(),
|
|
120
|
-
body: JSON.stringify(body),
|
|
121
|
-
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS),
|
|
122
|
-
});
|
|
123
|
-
if (!res.ok) {
|
|
124
|
-
const text = await res.text().catch(() => '');
|
|
125
|
-
throw new Error(`Failed to retain memory (HTTP ${res.status}): ${text}`);
|
|
126
|
-
}
|
|
127
|
-
const data = await res.json();
|
|
128
|
-
log.verbose(`retained via HTTP (async): ${JSON.stringify(data).substring(0, 200)}`);
|
|
129
|
-
return {
|
|
130
|
-
message: 'Memory queued for background processing',
|
|
131
|
-
document_id: request.document_id || 'conversation',
|
|
132
|
-
memory_unit_ids: [],
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
async retainSubprocess(request) {
|
|
136
|
-
const docId = request.document_id || 'conversation';
|
|
137
|
-
// Write content to a temp file to avoid E2BIG (ARG_MAX) errors when passing
|
|
138
|
-
// large conversations as arguments.
|
|
139
|
-
const tempDir = join(tmpdir(), `hindsight_${randomBytes(8).toString('hex')}`);
|
|
140
|
-
const safeFilename = sanitizeFilename(docId);
|
|
141
|
-
const tempFile = join(tempDir, `${safeFilename}.txt`);
|
|
142
|
-
try {
|
|
143
|
-
await mkdir(tempDir, { recursive: true });
|
|
144
|
-
await writeFile(tempFile, sanitize(request.content), 'utf8');
|
|
145
|
-
const [cmd, ...baseArgs] = this.getEmbedCommand();
|
|
146
|
-
const args = [...baseArgs, '--profile', 'openclaw', 'memory', 'retain-files', this.bankId, tempFile, '--async'];
|
|
147
|
-
const { stdout } = await execFileAsync(cmd, args, { maxBuffer: MAX_BUFFER });
|
|
148
|
-
log.verbose(`retained (async): ${stdout.trim()}`);
|
|
149
|
-
return {
|
|
150
|
-
message: 'Memory queued for background processing',
|
|
151
|
-
document_id: docId,
|
|
152
|
-
memory_unit_ids: [],
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
throw new Error(`Failed to retain memory: ${error}`, { cause: error });
|
|
157
|
-
}
|
|
158
|
-
finally {
|
|
159
|
-
await rm(tempDir, { recursive: true, force: true }).catch(() => { });
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
// --- recall ---
|
|
163
|
-
async recall(request, timeoutMs) {
|
|
164
|
-
if (this.httpMode) {
|
|
165
|
-
return this.recallHttp(request, timeoutMs);
|
|
166
|
-
}
|
|
167
|
-
return this.recallSubprocess(request, timeoutMs);
|
|
168
|
-
}
|
|
169
|
-
async recallHttp(request, timeoutMs) {
|
|
170
|
-
const url = `${this.apiUrl}/v1/default/banks/${encodeURIComponent(this.bankId)}/memories/recall`;
|
|
171
|
-
// Defense-in-depth: truncate query to stay under API's 500-token limit
|
|
172
|
-
const MAX_QUERY_CHARS = 800;
|
|
173
|
-
const query = request.query.length > MAX_QUERY_CHARS
|
|
174
|
-
? (log.warn(`truncating recall query from ${request.query.length} to ${MAX_QUERY_CHARS} chars`),
|
|
175
|
-
request.query.substring(0, MAX_QUERY_CHARS))
|
|
176
|
-
: request.query;
|
|
177
|
-
const body = {
|
|
178
|
-
query,
|
|
179
|
-
max_tokens: request.max_tokens || 1024,
|
|
180
|
-
};
|
|
181
|
-
if (request.budget) {
|
|
182
|
-
body.budget = request.budget;
|
|
183
|
-
}
|
|
184
|
-
if (request.types) {
|
|
185
|
-
body.types = request.types;
|
|
186
|
-
}
|
|
187
|
-
const res = await fetch(url, {
|
|
188
|
-
method: 'POST',
|
|
189
|
-
headers: this.httpHeaders(),
|
|
190
|
-
body: JSON.stringify(body),
|
|
191
|
-
signal: AbortSignal.timeout(timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
192
|
-
});
|
|
193
|
-
if (!res.ok) {
|
|
194
|
-
const text = await res.text().catch(() => '');
|
|
195
|
-
throw new Error(`Failed to recall memories (HTTP ${res.status}): ${text}`);
|
|
196
|
-
}
|
|
197
|
-
return res.json();
|
|
198
|
-
}
|
|
199
|
-
async recallSubprocess(request, timeoutMs) {
|
|
200
|
-
const query = sanitize(request.query);
|
|
201
|
-
const maxTokens = request.max_tokens || 1024;
|
|
202
|
-
const [cmd, ...baseArgs] = this.getEmbedCommand();
|
|
203
|
-
const args = [...baseArgs, '--profile', 'openclaw', 'memory', 'recall', this.bankId, query, '--output', 'json', '--max-tokens', String(maxTokens)];
|
|
204
|
-
try {
|
|
205
|
-
const { stdout } = await execFileAsync(cmd, args, {
|
|
206
|
-
maxBuffer: MAX_BUFFER,
|
|
207
|
-
timeout: timeoutMs ?? 30_000, // subprocess gets a longer default
|
|
208
|
-
});
|
|
209
|
-
return JSON.parse(stdout);
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
throw new Error(`Failed to recall memories: ${error}`, { cause: error });
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
package/dist/embed-manager.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export declare class HindsightEmbedManager {
|
|
2
|
-
private process;
|
|
3
|
-
private port;
|
|
4
|
-
private baseUrl;
|
|
5
|
-
private embedDir;
|
|
6
|
-
private llmProvider;
|
|
7
|
-
private llmApiKey;
|
|
8
|
-
private llmModel?;
|
|
9
|
-
private llmBaseUrl?;
|
|
10
|
-
private daemonIdleTimeout;
|
|
11
|
-
private embedVersion;
|
|
12
|
-
private embedPackagePath?;
|
|
13
|
-
constructor(port: number, llmProvider: string, llmApiKey: string, llmModel?: string, llmBaseUrl?: string, daemonIdleTimeout?: number, // Default: never timeout
|
|
14
|
-
embedVersion?: string, // Default: latest
|
|
15
|
-
embedPackagePath?: string);
|
|
16
|
-
/**
|
|
17
|
-
* Get the command to run hindsight-embed (either local or from PyPI)
|
|
18
|
-
*/
|
|
19
|
-
private getEmbedCommand;
|
|
20
|
-
start(): Promise<void>;
|
|
21
|
-
stop(): Promise<void>;
|
|
22
|
-
private waitForReady;
|
|
23
|
-
getBaseUrl(): string;
|
|
24
|
-
isRunning(): boolean;
|
|
25
|
-
checkHealth(): Promise<boolean>;
|
|
26
|
-
private configureProfile;
|
|
27
|
-
}
|