@vectorize-io/hindsight-openclaw 0.5.0 → 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.
@@ -1,210 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import { join } from 'path';
3
- import { homedir } from 'os';
4
- export class HindsightEmbedManager {
5
- process = null;
6
- port;
7
- baseUrl;
8
- embedDir;
9
- llmProvider;
10
- llmApiKey;
11
- llmModel;
12
- llmBaseUrl;
13
- daemonIdleTimeout;
14
- embedVersion;
15
- embedPackagePath;
16
- constructor(port, llmProvider, llmApiKey, llmModel, llmBaseUrl, daemonIdleTimeout = 0, // Default: never timeout
17
- embedVersion = 'latest', // Default: latest
18
- embedPackagePath // Local path to hindsight package
19
- ) {
20
- // Use the configured port (default: 9077 from config)
21
- this.port = port;
22
- this.baseUrl = `http://127.0.0.1:${port}`;
23
- this.embedDir = join(homedir(), '.openclaw', 'hindsight-embed');
24
- this.llmProvider = llmProvider;
25
- this.llmApiKey = llmApiKey;
26
- this.llmModel = llmModel;
27
- this.llmBaseUrl = llmBaseUrl;
28
- this.daemonIdleTimeout = daemonIdleTimeout;
29
- this.embedVersion = embedVersion || 'latest';
30
- this.embedPackagePath = embedPackagePath;
31
- }
32
- /**
33
- * Get the command to run hindsight-embed (either local or from PyPI)
34
- */
35
- getEmbedCommand() {
36
- if (this.embedPackagePath) {
37
- // Local package: uv run --directory <path> hindsight-embed
38
- return ['uv', 'run', '--directory', this.embedPackagePath, 'hindsight-embed'];
39
- }
40
- else {
41
- // PyPI package: uvx hindsight-embed@version
42
- const embedPackage = this.embedVersion ? `hindsight-embed@${this.embedVersion}` : 'hindsight-embed@latest';
43
- return ['uvx', embedPackage];
44
- }
45
- }
46
- async start() {
47
- console.log(`[Hindsight] Starting hindsight-embed daemon...`);
48
- // Build environment variables using standard HINDSIGHT_API_LLM_* variables
49
- const env = {
50
- ...process.env,
51
- HINDSIGHT_API_LLM_PROVIDER: this.llmProvider,
52
- HINDSIGHT_API_LLM_API_KEY: this.llmApiKey,
53
- HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT: this.daemonIdleTimeout.toString(),
54
- };
55
- if (this.llmModel) {
56
- env['HINDSIGHT_API_LLM_MODEL'] = this.llmModel;
57
- }
58
- // Pass through base URL for OpenAI-compatible providers (OpenRouter, etc.)
59
- if (this.llmBaseUrl) {
60
- env['HINDSIGHT_API_LLM_BASE_URL'] = this.llmBaseUrl;
61
- }
62
- // On macOS, force CPU for embeddings/reranker to avoid MPS/Metal issues in daemon mode
63
- if (process.platform === 'darwin') {
64
- env['HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU'] = '1';
65
- env['HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU'] = '1';
66
- }
67
- // Configure "openclaw" profile using hindsight-embed configure (non-interactive)
68
- console.log('[Hindsight] Configuring "openclaw" profile...');
69
- await this.configureProfile(env);
70
- // Start hindsight-embed daemon with openclaw profile
71
- const embedCmd = this.getEmbedCommand();
72
- const startDaemon = spawn(embedCmd[0], [...embedCmd.slice(1), 'daemon', '--profile', 'openclaw', 'start'], {
73
- stdio: 'pipe',
74
- });
75
- // Collect output
76
- let output = '';
77
- startDaemon.stdout?.on('data', (data) => {
78
- const text = data.toString();
79
- output += text;
80
- console.log(`[Hindsight] ${text.trim()}`);
81
- });
82
- startDaemon.stderr?.on('data', (data) => {
83
- const text = data.toString();
84
- output += text;
85
- console.error(`[Hindsight] ${text.trim()}`);
86
- });
87
- // Wait for daemon start command to complete
88
- await new Promise((resolve, reject) => {
89
- startDaemon.on('exit', (code) => {
90
- if (code === 0) {
91
- console.log('[Hindsight] Daemon start command completed');
92
- resolve();
93
- }
94
- else {
95
- reject(new Error(`Daemon start failed with code ${code}: ${output}`));
96
- }
97
- });
98
- startDaemon.on('error', (error) => {
99
- reject(error);
100
- });
101
- });
102
- // Wait for server to be ready
103
- await this.waitForReady();
104
- console.log('[Hindsight] Daemon is ready');
105
- }
106
- async stop() {
107
- console.log('[Hindsight] Stopping hindsight-embed daemon...');
108
- const embedCmd = this.getEmbedCommand();
109
- const stopDaemon = spawn(embedCmd[0], [...embedCmd.slice(1), 'daemon', '--profile', 'openclaw', 'stop'], {
110
- stdio: 'pipe',
111
- });
112
- await new Promise((resolve) => {
113
- stopDaemon.on('exit', () => {
114
- console.log('[Hindsight] Daemon stopped');
115
- resolve();
116
- });
117
- stopDaemon.on('error', (error) => {
118
- console.error('[Hindsight] Error stopping daemon:', error);
119
- resolve(); // Resolve anyway
120
- });
121
- // Timeout after 5 seconds
122
- setTimeout(() => {
123
- console.log('[Hindsight] Daemon stop timeout');
124
- resolve();
125
- }, 5000);
126
- });
127
- }
128
- async waitForReady(maxAttempts = 30) {
129
- console.log('[Hindsight] Waiting for daemon to be ready...');
130
- for (let i = 0; i < maxAttempts; i++) {
131
- try {
132
- const response = await fetch(`${this.baseUrl}/health`);
133
- if (response.ok) {
134
- console.log('[Hindsight] Daemon health check passed');
135
- return;
136
- }
137
- }
138
- catch {
139
- // Not ready yet
140
- }
141
- await new Promise((resolve) => setTimeout(resolve, 1000));
142
- }
143
- throw new Error('Hindsight daemon failed to become ready within 30 seconds');
144
- }
145
- getBaseUrl() {
146
- return this.baseUrl;
147
- }
148
- isRunning() {
149
- return this.process !== null;
150
- }
151
- async checkHealth() {
152
- try {
153
- const response = await fetch(`${this.baseUrl}/health`, { signal: AbortSignal.timeout(2000) });
154
- return response.ok;
155
- }
156
- catch {
157
- return false;
158
- }
159
- }
160
- async configureProfile(env) {
161
- // Build profile create command args with --merge, --port and --env flags
162
- // Use --merge to allow updating existing profile
163
- const createArgs = ['profile', 'create', 'openclaw', '--merge', '--port', this.port.toString()];
164
- // Add all environment variables as --env flags
165
- const envVars = [
166
- 'HINDSIGHT_API_LLM_PROVIDER',
167
- 'HINDSIGHT_API_LLM_MODEL',
168
- 'HINDSIGHT_API_LLM_API_KEY',
169
- 'HINDSIGHT_API_LLM_BASE_URL',
170
- 'HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT',
171
- 'HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU',
172
- 'HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU',
173
- ];
174
- for (const envVar of envVars) {
175
- if (env[envVar]) {
176
- createArgs.push('--env', `${envVar}=${env[envVar]}`);
177
- }
178
- }
179
- // Run profile create command (non-interactive, overwrites if exists)
180
- const embedCmd = this.getEmbedCommand();
181
- const create = spawn(embedCmd[0], [...embedCmd.slice(1), ...createArgs], {
182
- stdio: 'pipe',
183
- });
184
- let output = '';
185
- create.stdout?.on('data', (data) => {
186
- const text = data.toString();
187
- output += text;
188
- console.log(`[Hindsight] ${text.trim()}`);
189
- });
190
- create.stderr?.on('data', (data) => {
191
- const text = data.toString();
192
- output += text;
193
- console.error(`[Hindsight] ${text.trim()}`);
194
- });
195
- await new Promise((resolve, reject) => {
196
- create.on('exit', (code) => {
197
- if (code === 0) {
198
- console.log('[Hindsight] Profile "openclaw" configured successfully');
199
- resolve();
200
- }
201
- else {
202
- reject(new Error(`Profile create failed with code ${code}: ${output}`));
203
- }
204
- });
205
- create.on('error', (error) => {
206
- reject(error);
207
- });
208
- });
209
- }
210
- }