@vectorize-io/hindsight-openclaw 0.4.6 → 0.4.7
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/embed-manager.d.ts +3 -1
- package/dist/embed-manager.js +34 -3
- package/dist/index.js +100 -66
- package/package.json +1 -1
package/dist/embed-manager.d.ts
CHANGED
|
@@ -6,14 +6,16 @@ export declare class HindsightEmbedManager {
|
|
|
6
6
|
private llmProvider;
|
|
7
7
|
private llmApiKey;
|
|
8
8
|
private llmModel?;
|
|
9
|
+
private llmBaseUrl?;
|
|
9
10
|
private daemonIdleTimeout;
|
|
10
11
|
private embedVersion;
|
|
11
|
-
constructor(port: number, llmProvider: string, llmApiKey: string, llmModel?: string, daemonIdleTimeout?: number, // Default: never timeout
|
|
12
|
+
constructor(port: number, llmProvider: string, llmApiKey: string, llmModel?: string, llmBaseUrl?: string, daemonIdleTimeout?: number, // Default: never timeout
|
|
12
13
|
embedVersion?: string);
|
|
13
14
|
start(): Promise<void>;
|
|
14
15
|
stop(): Promise<void>;
|
|
15
16
|
private waitForReady;
|
|
16
17
|
getBaseUrl(): string;
|
|
17
18
|
isRunning(): boolean;
|
|
19
|
+
checkHealth(): Promise<boolean>;
|
|
18
20
|
private writeConfigEnv;
|
|
19
21
|
}
|
package/dist/embed-manager.js
CHANGED
|
@@ -10,17 +10,19 @@ export class HindsightEmbedManager {
|
|
|
10
10
|
llmProvider;
|
|
11
11
|
llmApiKey;
|
|
12
12
|
llmModel;
|
|
13
|
+
llmBaseUrl;
|
|
13
14
|
daemonIdleTimeout;
|
|
14
15
|
embedVersion;
|
|
15
|
-
constructor(port, llmProvider, llmApiKey, llmModel, daemonIdleTimeout = 0, // Default: never timeout
|
|
16
|
+
constructor(port, llmProvider, llmApiKey, llmModel, llmBaseUrl, daemonIdleTimeout = 0, // Default: never timeout
|
|
16
17
|
embedVersion = 'latest' // Default: latest
|
|
17
18
|
) {
|
|
18
|
-
this.port =
|
|
19
|
-
this.baseUrl = `http://127.0.0.1:
|
|
19
|
+
this.port = 8888; // hindsight-embed daemon uses same port as API
|
|
20
|
+
this.baseUrl = `http://127.0.0.1:8888`;
|
|
20
21
|
this.embedDir = join(homedir(), '.openclaw', 'hindsight-embed');
|
|
21
22
|
this.llmProvider = llmProvider;
|
|
22
23
|
this.llmApiKey = llmApiKey;
|
|
23
24
|
this.llmModel = llmModel;
|
|
25
|
+
this.llmBaseUrl = llmBaseUrl;
|
|
24
26
|
this.daemonIdleTimeout = daemonIdleTimeout;
|
|
25
27
|
this.embedVersion = embedVersion || 'latest';
|
|
26
28
|
}
|
|
@@ -36,6 +38,15 @@ export class HindsightEmbedManager {
|
|
|
36
38
|
if (this.llmModel) {
|
|
37
39
|
env['HINDSIGHT_EMBED_LLM_MODEL'] = this.llmModel;
|
|
38
40
|
}
|
|
41
|
+
// Pass through base URL for OpenAI-compatible providers (OpenRouter, etc.)
|
|
42
|
+
if (this.llmBaseUrl) {
|
|
43
|
+
env['HINDSIGHT_API_LLM_BASE_URL'] = this.llmBaseUrl;
|
|
44
|
+
}
|
|
45
|
+
// On macOS, force CPU for embeddings/reranker to avoid MPS/Metal issues in daemon mode
|
|
46
|
+
if (process.platform === 'darwin') {
|
|
47
|
+
env['HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU'] = '1';
|
|
48
|
+
env['HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU'] = '1';
|
|
49
|
+
}
|
|
39
50
|
// Write env vars to ~/.hindsight/config.env for daemon persistence
|
|
40
51
|
await this.writeConfigEnv(env);
|
|
41
52
|
// Start hindsight-embed daemon (it manages itself)
|
|
@@ -120,6 +131,15 @@ export class HindsightEmbedManager {
|
|
|
120
131
|
isRunning() {
|
|
121
132
|
return this.process !== null;
|
|
122
133
|
}
|
|
134
|
+
async checkHealth() {
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch(`${this.baseUrl}/health`, { signal: AbortSignal.timeout(2000) });
|
|
137
|
+
return response.ok;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
123
143
|
async writeConfigEnv(env) {
|
|
124
144
|
const hindsightDir = join(homedir(), '.hindsight');
|
|
125
145
|
const embedConfigPath = join(hindsightDir, 'embed');
|
|
@@ -136,6 +156,7 @@ export class HindsightEmbedManager {
|
|
|
136
156
|
const trimmed = line.trim();
|
|
137
157
|
if (trimmed && !trimmed.startsWith('#') &&
|
|
138
158
|
!trimmed.startsWith('HINDSIGHT_EMBED_LLM_') &&
|
|
159
|
+
!trimmed.startsWith('HINDSIGHT_API_LLM_') &&
|
|
139
160
|
!trimmed.startsWith('HINDSIGHT_EMBED_BANK_ID') &&
|
|
140
161
|
!trimmed.startsWith('HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT')) {
|
|
141
162
|
extraSettings.push(line);
|
|
@@ -161,9 +182,19 @@ export class HindsightEmbedManager {
|
|
|
161
182
|
if (env.HINDSIGHT_EMBED_LLM_API_KEY) {
|
|
162
183
|
configLines.push(`HINDSIGHT_EMBED_LLM_API_KEY=${env.HINDSIGHT_EMBED_LLM_API_KEY}`);
|
|
163
184
|
}
|
|
185
|
+
if (env.HINDSIGHT_API_LLM_BASE_URL) {
|
|
186
|
+
configLines.push(`HINDSIGHT_API_LLM_BASE_URL=${env.HINDSIGHT_API_LLM_BASE_URL}`);
|
|
187
|
+
}
|
|
164
188
|
if (env.HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT) {
|
|
165
189
|
configLines.push(`HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT=${env.HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT}`);
|
|
166
190
|
}
|
|
191
|
+
// Add platform-specific config (macOS FORCE_CPU flags)
|
|
192
|
+
if (env.HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU) {
|
|
193
|
+
configLines.push(`HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU=${env.HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU}`);
|
|
194
|
+
}
|
|
195
|
+
if (env.HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU) {
|
|
196
|
+
configLines.push(`HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU=${env.HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU}`);
|
|
197
|
+
}
|
|
167
198
|
// Add extra settings if they exist
|
|
168
199
|
if (extraSettings.length > 0) {
|
|
169
200
|
configLines.push('');
|
package/dist/index.js
CHANGED
|
@@ -24,69 +24,65 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
24
24
|
const __dirname = dirname(__filename);
|
|
25
25
|
// Default bank name
|
|
26
26
|
const BANK_NAME = 'openclaw';
|
|
27
|
-
// Provider
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Get models from config (agents.defaults.models is a dictionary of models)
|
|
47
|
-
const models = api.config.agents?.defaults?.models;
|
|
48
|
-
if (!models || Object.keys(models).length === 0) {
|
|
49
|
-
throw new Error('No models configured in Moltbot. Please configure at least one model in agents.defaults.models');
|
|
50
|
-
}
|
|
51
|
-
// Try all configured models to find one with an available API key
|
|
52
|
-
const configuredModels = Object.keys(models);
|
|
53
|
-
for (const modelKey of configuredModels) {
|
|
54
|
-
const [moltbotProvider, ...modelParts] = modelKey.split('/');
|
|
55
|
-
const model = modelParts.join('/');
|
|
56
|
-
const hindsightProvider = PROVIDER_MAP[moltbotProvider];
|
|
57
|
-
if (!hindsightProvider) {
|
|
58
|
-
continue; // Skip unsupported providers
|
|
27
|
+
// Provider detection from standard env vars
|
|
28
|
+
const PROVIDER_DETECTION = [
|
|
29
|
+
{ name: 'openai', keyEnv: 'OPENAI_API_KEY', defaultModel: 'gpt-4o-mini' },
|
|
30
|
+
{ name: 'anthropic', keyEnv: 'ANTHROPIC_API_KEY', defaultModel: 'claude-3-5-haiku-20241022' },
|
|
31
|
+
{ name: 'gemini', keyEnv: 'GEMINI_API_KEY', defaultModel: 'gemini-2.5-flash' },
|
|
32
|
+
{ name: 'groq', keyEnv: 'GROQ_API_KEY', defaultModel: 'openai/gpt-oss-20b' },
|
|
33
|
+
{ name: 'ollama', keyEnv: '', defaultModel: 'llama3.2' },
|
|
34
|
+
];
|
|
35
|
+
function detectLLMConfig() {
|
|
36
|
+
// Override values from HINDSIGHT_API_LLM_* env vars (highest priority)
|
|
37
|
+
const overrideProvider = process.env.HINDSIGHT_API_LLM_PROVIDER;
|
|
38
|
+
const overrideModel = process.env.HINDSIGHT_API_LLM_MODEL;
|
|
39
|
+
const overrideKey = process.env.HINDSIGHT_API_LLM_API_KEY;
|
|
40
|
+
const overrideBaseUrl = process.env.HINDSIGHT_API_LLM_BASE_URL;
|
|
41
|
+
// If provider is explicitly set, use that (with overrides)
|
|
42
|
+
if (overrideProvider) {
|
|
43
|
+
if (!overrideKey && overrideProvider !== 'ollama') {
|
|
44
|
+
throw new Error(`HINDSIGHT_API_LLM_PROVIDER is set to "${overrideProvider}" but HINDSIGHT_API_LLM_API_KEY is not set.\n` +
|
|
45
|
+
`Please set: export HINDSIGHT_API_LLM_API_KEY=your-api-key`);
|
|
59
46
|
}
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
47
|
+
const providerInfo = PROVIDER_DETECTION.find(p => p.name === overrideProvider);
|
|
48
|
+
return {
|
|
49
|
+
provider: overrideProvider,
|
|
50
|
+
apiKey: overrideKey || '',
|
|
51
|
+
model: overrideModel || (providerInfo?.defaultModel),
|
|
52
|
+
baseUrl: overrideBaseUrl,
|
|
53
|
+
source: 'HINDSIGHT_API_LLM_PROVIDER override',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Auto-detect from standard provider env vars
|
|
57
|
+
for (const providerInfo of PROVIDER_DETECTION) {
|
|
58
|
+
const apiKey = providerInfo.keyEnv ? process.env[providerInfo.keyEnv] : '';
|
|
59
|
+
// Skip ollama in auto-detection (must be explicitly requested)
|
|
60
|
+
if (providerInfo.name === 'ollama') {
|
|
61
|
+
continue;
|
|
65
62
|
}
|
|
66
|
-
// If we found a key, use this provider
|
|
67
63
|
if (apiKey) {
|
|
68
|
-
return {
|
|
64
|
+
return {
|
|
65
|
+
provider: providerInfo.name,
|
|
66
|
+
apiKey,
|
|
67
|
+
model: overrideModel || providerInfo.defaultModel,
|
|
68
|
+
baseUrl: overrideBaseUrl, // Only use explicit HINDSIGHT_API_LLM_BASE_URL
|
|
69
|
+
source: `auto-detected from ${providerInfo.keyEnv}`,
|
|
70
|
+
};
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
|
-
// No
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
`
|
|
84
|
-
`Please set one of these environment variables:\n${keyInstructions}\n\n` +
|
|
85
|
-
`You can set them in your shell profile (~/.zshrc or ~/.bashrc):\n` +
|
|
86
|
-
` export ANTHROPIC_API_KEY="your-key-here"\n\n` +
|
|
87
|
-
`Or run OpenClaw with the environment variable:\n` +
|
|
88
|
-
` ANTHROPIC_API_KEY="your-key" openclaw gateway\n\n` +
|
|
89
|
-
`Alternatively, configure ollama provider which doesn't require an API key.`);
|
|
73
|
+
// No configuration found - show helpful error
|
|
74
|
+
throw new Error(`No LLM configuration found for Hindsight memory plugin.\n\n` +
|
|
75
|
+
`Option 1: Set a standard provider API key (auto-detect):\n` +
|
|
76
|
+
` export OPENAI_API_KEY=sk-your-key # Uses gpt-4o-mini\n` +
|
|
77
|
+
` export ANTHROPIC_API_KEY=your-key # Uses claude-3-5-haiku\n` +
|
|
78
|
+
` export GEMINI_API_KEY=your-key # Uses gemini-2.0-flash-exp\n` +
|
|
79
|
+
` export GROQ_API_KEY=your-key # Uses llama-3.3-70b-versatile\n\n` +
|
|
80
|
+
`Option 2: Override with Hindsight-specific config:\n` +
|
|
81
|
+
` export HINDSIGHT_API_LLM_PROVIDER=openai\n` +
|
|
82
|
+
` export HINDSIGHT_API_LLM_MODEL=gpt-4o-mini\n` +
|
|
83
|
+
` export HINDSIGHT_API_LLM_API_KEY=sk-your-key\n` +
|
|
84
|
+
` export HINDSIGHT_API_LLM_BASE_URL=https://openrouter.ai/api/v1 # Optional\n\n` +
|
|
85
|
+
`Tip: Use a cheap/fast model for memory extraction (e.g., gpt-4o-mini, claude-3-5-haiku, or free models on OpenRouter)`);
|
|
90
86
|
}
|
|
91
87
|
function getPluginConfig(api) {
|
|
92
88
|
const config = api.config.plugins?.entries?.['hindsight-openclaw']?.config || {};
|
|
@@ -101,14 +97,16 @@ function getPluginConfig(api) {
|
|
|
101
97
|
export default function (api) {
|
|
102
98
|
try {
|
|
103
99
|
console.log('[Hindsight] Plugin loading...');
|
|
104
|
-
// Detect LLM configuration from
|
|
100
|
+
// Detect LLM configuration from environment
|
|
105
101
|
console.log('[Hindsight] Detecting LLM config...');
|
|
106
|
-
const llmConfig = detectLLMConfig(
|
|
102
|
+
const llmConfig = detectLLMConfig();
|
|
103
|
+
const baseUrlInfo = llmConfig.baseUrl ? `, base URL: ${llmConfig.baseUrl}` : '';
|
|
104
|
+
const modelInfo = llmConfig.model || 'default';
|
|
107
105
|
if (llmConfig.provider === 'ollama') {
|
|
108
|
-
console.log(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${llmConfig.
|
|
106
|
+
console.log(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${modelInfo} (${llmConfig.source})`);
|
|
109
107
|
}
|
|
110
108
|
else {
|
|
111
|
-
console.log(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${
|
|
109
|
+
console.log(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${modelInfo} (${llmConfig.source}${baseUrlInfo})`);
|
|
112
110
|
}
|
|
113
111
|
console.log('[Hindsight] Getting plugin config...');
|
|
114
112
|
const pluginConfig = getPluginConfig(api);
|
|
@@ -125,7 +123,7 @@ export default function (api) {
|
|
|
125
123
|
try {
|
|
126
124
|
// Initialize embed manager
|
|
127
125
|
console.log('[Hindsight] Creating HindsightEmbedManager...');
|
|
128
|
-
embedManager = new HindsightEmbedManager(port, llmConfig.provider, llmConfig.apiKey, llmConfig.model, pluginConfig.daemonIdleTimeout, pluginConfig.embedVersion);
|
|
126
|
+
embedManager = new HindsightEmbedManager(port, llmConfig.provider, llmConfig.apiKey, llmConfig.model, llmConfig.baseUrl, pluginConfig.daemonIdleTimeout, pluginConfig.embedVersion);
|
|
129
127
|
// Start the embedded server
|
|
130
128
|
console.log('[Hindsight] Starting embedded server...');
|
|
131
129
|
await embedManager.start();
|
|
@@ -154,10 +152,46 @@ export default function (api) {
|
|
|
154
152
|
api.registerService({
|
|
155
153
|
id: 'hindsight-memory',
|
|
156
154
|
async start() {
|
|
155
|
+
console.log('[Hindsight] Service start called - checking daemon health...');
|
|
157
156
|
// Wait for background init if still pending
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
if (initPromise) {
|
|
158
|
+
try {
|
|
159
|
+
await initPromise;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
console.error('[Hindsight] Initial initialization failed:', error);
|
|
163
|
+
// Continue to health check below
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Check if daemon is actually healthy (handles SIGUSR1 restart case)
|
|
167
|
+
if (embedManager && isInitialized) {
|
|
168
|
+
const healthy = await embedManager.checkHealth();
|
|
169
|
+
if (healthy) {
|
|
170
|
+
console.log('[Hindsight] Daemon is healthy');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
console.log('[Hindsight] Daemon is not responding - reinitializing...');
|
|
174
|
+
// Reset state for reinitialization
|
|
175
|
+
embedManager = null;
|
|
176
|
+
client = null;
|
|
177
|
+
isInitialized = false;
|
|
178
|
+
}
|
|
179
|
+
// Reinitialize if needed (fresh start or recovery from dead daemon)
|
|
180
|
+
if (!isInitialized) {
|
|
181
|
+
console.log('[Hindsight] Reinitializing daemon...');
|
|
182
|
+
const llmConfig = detectLLMConfig();
|
|
183
|
+
const pluginConfig = getPluginConfig(api);
|
|
184
|
+
const port = pluginConfig.embedPort || Math.floor(Math.random() * 10000) + 10000;
|
|
185
|
+
embedManager = new HindsightEmbedManager(port, llmConfig.provider, llmConfig.apiKey, llmConfig.model, llmConfig.baseUrl, pluginConfig.daemonIdleTimeout, pluginConfig.embedVersion);
|
|
186
|
+
await embedManager.start();
|
|
187
|
+
client = new HindsightClient(llmConfig.provider, llmConfig.apiKey, llmConfig.model, pluginConfig.embedVersion);
|
|
188
|
+
client.setBankId(BANK_NAME);
|
|
189
|
+
if (pluginConfig.bankMission) {
|
|
190
|
+
await client.setBankMission(pluginConfig.bankMission);
|
|
191
|
+
}
|
|
192
|
+
isInitialized = true;
|
|
193
|
+
console.log('[Hindsight] Reinitialization complete');
|
|
194
|
+
}
|
|
161
195
|
},
|
|
162
196
|
async stop() {
|
|
163
197
|
try {
|
package/package.json
CHANGED