@vectorize-io/hindsight-openclaw 0.4.7 → 0.4.8

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 CHANGED
@@ -5,9 +5,15 @@ Biomimetic long-term memory for [OpenClaw](https://openclaw.ai) using [Hindsight
5
5
  ## Quick Start
6
6
 
7
7
  ```bash
8
- # 1. Configure your LLM provider
8
+ # 1. Configure your LLM provider for memory extraction
9
+ # Option A: OpenAI
9
10
  export OPENAI_API_KEY="sk-your-key"
10
- openclaw config set 'agents.defaults.models."openai/gpt-4o-mini"' '{}'
11
+
12
+ # Option B: Claude Code (no API key needed)
13
+ export HINDSIGHT_API_LLM_PROVIDER=claude-code
14
+
15
+ # Option C: OpenAI Codex (no API key needed)
16
+ export HINDSIGHT_API_LLM_PROVIDER=openai-codex
11
17
 
12
18
  # 2. Install and enable the plugin
13
19
  openclaw plugins install @vectorize-io/hindsight-openclaw
@@ -24,6 +30,40 @@ For full documentation, configuration options, troubleshooting, and development
24
30
 
25
31
  **[OpenClaw Integration Documentation](https://vectorize.io/hindsight/sdks/integrations/openclaw)**
26
32
 
33
+ ## Development
34
+
35
+ To test local changes to the Hindsight package before publishing:
36
+
37
+ 1. Add `embedPackagePath` to your plugin config in `~/.openclaw/openclaw.json`:
38
+ ```json
39
+ {
40
+ "plugins": {
41
+ "entries": {
42
+ "hindsight-openclaw": {
43
+ "enabled": true,
44
+ "config": {
45
+ "embedPackagePath": "/path/to/hindsight-wt3/hindsight-embed"
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ 2. The plugin will use `uv run --directory <path> hindsight-embed` instead of `uvx hindsight-embed@latest`
54
+
55
+ 3. To use a specific profile for testing:
56
+ ```bash
57
+ # Check daemon status
58
+ uvx hindsight-embed@latest -p openclaw daemon status
59
+
60
+ # View logs
61
+ tail -f ~/.hindsight/profiles/openclaw.log
62
+
63
+ # List profiles
64
+ uvx hindsight-embed@latest profile list
65
+ ```
66
+
27
67
  ## Links
28
68
 
29
69
  - [Hindsight Documentation](https://vectorize.io/hindsight)
package/dist/client.d.ts CHANGED
@@ -5,10 +5,14 @@ export declare class HindsightClient {
5
5
  private llmApiKey;
6
6
  private llmModel?;
7
7
  private embedVersion;
8
- constructor(llmProvider: string, llmApiKey: string, llmModel?: string, embedVersion?: string);
8
+ private embedPackagePath?;
9
+ constructor(llmProvider: string, llmApiKey: string, llmModel?: string, embedVersion?: string, embedPackagePath?: string);
10
+ /**
11
+ * Get the command prefix to run hindsight-embed (either local or from PyPI)
12
+ */
13
+ private getEmbedCommandPrefix;
9
14
  setBankId(bankId: string): void;
10
15
  setBankMission(mission: string): Promise<void>;
11
- private getEnv;
12
16
  retain(request: RetainRequest): Promise<RetainResponse>;
13
17
  recall(request: RecallRequest): Promise<RecallResponse>;
14
18
  }
package/dist/client.js CHANGED
@@ -7,11 +7,27 @@ export class HindsightClient {
7
7
  llmApiKey;
8
8
  llmModel;
9
9
  embedVersion;
10
- constructor(llmProvider, llmApiKey, llmModel, embedVersion = 'latest') {
10
+ embedPackagePath;
11
+ constructor(llmProvider, llmApiKey, llmModel, embedVersion = 'latest', embedPackagePath) {
11
12
  this.llmProvider = llmProvider;
12
13
  this.llmApiKey = llmApiKey;
13
14
  this.llmModel = llmModel;
14
15
  this.embedVersion = embedVersion || 'latest';
16
+ this.embedPackagePath = embedPackagePath;
17
+ }
18
+ /**
19
+ * Get the command prefix to run hindsight-embed (either local or from PyPI)
20
+ */
21
+ getEmbedCommandPrefix() {
22
+ if (this.embedPackagePath) {
23
+ // Local package: uv run --directory <path> hindsight-embed
24
+ return `uv run --directory ${this.embedPackagePath} hindsight-embed`;
25
+ }
26
+ else {
27
+ // PyPI package: uvx hindsight-embed@version
28
+ const embedPackage = this.embedVersion ? `hindsight-embed@${this.embedVersion}` : 'hindsight-embed@latest';
29
+ return `uvx ${embedPackage}`;
30
+ }
15
31
  }
16
32
  setBankId(bankId) {
17
33
  this.bankId = bankId;
@@ -21,10 +37,10 @@ export class HindsightClient {
21
37
  return;
22
38
  }
23
39
  const escapedMission = mission.replace(/'/g, "'\\''"); // Escape single quotes
24
- const embedPackage = this.embedVersion ? `hindsight-embed@${this.embedVersion}` : 'hindsight-embed@latest';
25
- const cmd = `uvx ${embedPackage} bank mission ${this.bankId} '${escapedMission}'`;
40
+ const embedCmd = this.getEmbedCommandPrefix();
41
+ const cmd = `${embedCmd} --profile openclaw bank mission ${this.bankId} '${escapedMission}'`;
26
42
  try {
27
- const { stdout } = await execAsync(cmd, { env: this.getEnv() });
43
+ const { stdout } = await execAsync(cmd);
28
44
  console.log(`[Hindsight] Bank mission set: ${stdout.trim()}`);
29
45
  }
30
46
  catch (error) {
@@ -32,24 +48,13 @@ export class HindsightClient {
32
48
  console.warn(`[Hindsight] Could not set bank mission (bank may not exist yet): ${error}`);
33
49
  }
34
50
  }
35
- getEnv() {
36
- const env = {
37
- ...process.env,
38
- HINDSIGHT_EMBED_LLM_PROVIDER: this.llmProvider,
39
- HINDSIGHT_EMBED_LLM_API_KEY: this.llmApiKey,
40
- };
41
- if (this.llmModel) {
42
- env.HINDSIGHT_EMBED_LLM_MODEL = this.llmModel;
43
- }
44
- return env;
45
- }
46
51
  async retain(request) {
47
52
  const content = request.content.replace(/'/g, "'\\''"); // Escape single quotes
48
53
  const docId = request.document_id || 'conversation';
49
- const embedPackage = this.embedVersion ? `hindsight-embed@${this.embedVersion}` : 'hindsight-embed@latest';
50
- const cmd = `uvx ${embedPackage} memory retain ${this.bankId} '${content}' --doc-id '${docId}' --async`;
54
+ const embedCmd = this.getEmbedCommandPrefix();
55
+ const cmd = `${embedCmd} --profile openclaw memory retain ${this.bankId} '${content}' --doc-id '${docId}' --async`;
51
56
  try {
52
- const { stdout } = await execAsync(cmd, { env: this.getEnv() });
57
+ const { stdout } = await execAsync(cmd);
53
58
  console.log(`[Hindsight] Retained (async): ${stdout.trim()}`);
54
59
  // Return a simple response
55
60
  return {
@@ -65,10 +70,10 @@ export class HindsightClient {
65
70
  async recall(request) {
66
71
  const query = request.query.replace(/'/g, "'\\''"); // Escape single quotes
67
72
  const maxTokens = request.max_tokens || 1024;
68
- const embedPackage = this.embedVersion ? `hindsight-embed@${this.embedVersion}` : 'hindsight-embed@latest';
69
- const cmd = `uvx ${embedPackage} memory recall ${this.bankId} '${query}' --output json --max-tokens ${maxTokens}`;
73
+ const embedCmd = this.getEmbedCommandPrefix();
74
+ const cmd = `${embedCmd} --profile openclaw memory recall ${this.bankId} '${query}' --output json --max-tokens ${maxTokens}`;
70
75
  try {
71
- const { stdout } = await execAsync(cmd, { env: this.getEnv() });
76
+ const { stdout } = await execAsync(cmd);
72
77
  // Parse JSON output - returns { entities: {...}, results: [...] }
73
78
  const response = JSON.parse(stdout);
74
79
  const results = response.results || [];
@@ -9,13 +9,19 @@ export declare class HindsightEmbedManager {
9
9
  private llmBaseUrl?;
10
10
  private daemonIdleTimeout;
11
11
  private embedVersion;
12
+ private embedPackagePath?;
12
13
  constructor(port: number, llmProvider: string, llmApiKey: string, llmModel?: string, llmBaseUrl?: string, daemonIdleTimeout?: number, // Default: never timeout
13
- embedVersion?: string);
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;
14
20
  start(): Promise<void>;
15
21
  stop(): Promise<void>;
16
22
  private waitForReady;
17
23
  getBaseUrl(): string;
18
24
  isRunning(): boolean;
19
25
  checkHealth(): Promise<boolean>;
20
- private writeConfigEnv;
26
+ private configureProfile;
21
27
  }
@@ -1,5 +1,4 @@
1
1
  import { spawn } from 'child_process';
2
- import { promises as fs } from 'fs';
3
2
  import { join } from 'path';
4
3
  import { homedir } from 'os';
5
4
  export class HindsightEmbedManager {
@@ -13,11 +12,14 @@ export class HindsightEmbedManager {
13
12
  llmBaseUrl;
14
13
  daemonIdleTimeout;
15
14
  embedVersion;
15
+ embedPackagePath;
16
16
  constructor(port, llmProvider, llmApiKey, llmModel, llmBaseUrl, daemonIdleTimeout = 0, // Default: never timeout
17
- embedVersion = 'latest' // Default: latest
17
+ embedVersion = 'latest', // Default: latest
18
+ embedPackagePath // Local path to hindsight package
18
19
  ) {
19
- this.port = 8888; // hindsight-embed daemon uses same port as API
20
- this.baseUrl = `http://127.0.0.1:8888`;
20
+ // Use the configured port (default: 9077 from config)
21
+ this.port = port;
22
+ this.baseUrl = `http://127.0.0.1:${port}`;
21
23
  this.embedDir = join(homedir(), '.openclaw', 'hindsight-embed');
22
24
  this.llmProvider = llmProvider;
23
25
  this.llmApiKey = llmApiKey;
@@ -25,18 +27,33 @@ export class HindsightEmbedManager {
25
27
  this.llmBaseUrl = llmBaseUrl;
26
28
  this.daemonIdleTimeout = daemonIdleTimeout;
27
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
+ }
28
45
  }
29
46
  async start() {
30
47
  console.log(`[Hindsight] Starting hindsight-embed daemon...`);
31
- // Build environment variables
48
+ // Build environment variables using standard HINDSIGHT_API_LLM_* variables
32
49
  const env = {
33
50
  ...process.env,
34
- HINDSIGHT_EMBED_LLM_PROVIDER: this.llmProvider,
35
- HINDSIGHT_EMBED_LLM_API_KEY: this.llmApiKey,
51
+ HINDSIGHT_API_LLM_PROVIDER: this.llmProvider,
52
+ HINDSIGHT_API_LLM_API_KEY: this.llmApiKey,
36
53
  HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT: this.daemonIdleTimeout.toString(),
37
54
  };
38
55
  if (this.llmModel) {
39
- env['HINDSIGHT_EMBED_LLM_MODEL'] = this.llmModel;
56
+ env['HINDSIGHT_API_LLM_MODEL'] = this.llmModel;
40
57
  }
41
58
  // Pass through base URL for OpenAI-compatible providers (OpenRouter, etc.)
42
59
  if (this.llmBaseUrl) {
@@ -47,12 +64,12 @@ export class HindsightEmbedManager {
47
64
  env['HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU'] = '1';
48
65
  env['HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU'] = '1';
49
66
  }
50
- // Write env vars to ~/.hindsight/config.env for daemon persistence
51
- await this.writeConfigEnv(env);
52
- // Start hindsight-embed daemon (it manages itself)
53
- const embedPackage = this.embedVersion ? `hindsight-embed@${this.embedVersion}` : 'hindsight-embed@latest';
54
- const startDaemon = spawn('uvx', [embedPackage, 'daemon', 'start'], {
55
- env,
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'], {
56
73
  stdio: 'pipe',
57
74
  });
58
75
  // Collect output
@@ -88,8 +105,8 @@ export class HindsightEmbedManager {
88
105
  }
89
106
  async stop() {
90
107
  console.log('[Hindsight] Stopping hindsight-embed daemon...');
91
- const embedPackage = this.embedVersion ? `hindsight-embed@${this.embedVersion}` : 'hindsight-embed@latest';
92
- const stopDaemon = spawn('uvx', [embedPackage, 'daemon', 'stop'], {
108
+ const embedCmd = this.getEmbedCommand();
109
+ const stopDaemon = spawn(embedCmd[0], [...embedCmd.slice(1), 'daemon', '--profile', 'openclaw', 'stop'], {
93
110
  stdio: 'pipe',
94
111
  });
95
112
  await new Promise((resolve) => {
@@ -140,69 +157,54 @@ export class HindsightEmbedManager {
140
157
  return false;
141
158
  }
142
159
  }
143
- async writeConfigEnv(env) {
144
- const hindsightDir = join(homedir(), '.hindsight');
145
- const embedConfigPath = join(hindsightDir, 'embed');
146
- // Ensure directory exists
147
- await fs.mkdir(hindsightDir, { recursive: true });
148
- // Read existing config to preserve extra settings
149
- let existingContent = '';
150
- let extraSettings = [];
151
- try {
152
- existingContent = await fs.readFile(embedConfigPath, 'utf-8');
153
- // Extract non-LLM settings (like FORCE_CPU flags)
154
- const lines = existingContent.split('\n');
155
- for (const line of lines) {
156
- const trimmed = line.trim();
157
- if (trimmed && !trimmed.startsWith('#') &&
158
- !trimmed.startsWith('HINDSIGHT_EMBED_LLM_') &&
159
- !trimmed.startsWith('HINDSIGHT_API_LLM_') &&
160
- !trimmed.startsWith('HINDSIGHT_EMBED_BANK_ID') &&
161
- !trimmed.startsWith('HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT')) {
162
- extraSettings.push(line);
163
- }
164
- }
165
- }
166
- catch {
167
- // File doesn't exist yet, that's fine
168
- }
169
- // Build config file with header
170
- const configLines = [
171
- '# Hindsight Embed Configuration',
172
- '# Generated by OpenClaw Hindsight plugin',
173
- '',
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',
174
173
  ];
175
- // Add LLM config
176
- if (env.HINDSIGHT_EMBED_LLM_PROVIDER) {
177
- configLines.push(`HINDSIGHT_EMBED_LLM_PROVIDER=${env.HINDSIGHT_EMBED_LLM_PROVIDER}`);
178
- }
179
- if (env.HINDSIGHT_EMBED_LLM_MODEL) {
180
- configLines.push(`HINDSIGHT_EMBED_LLM_MODEL=${env.HINDSIGHT_EMBED_LLM_MODEL}`);
181
- }
182
- if (env.HINDSIGHT_EMBED_LLM_API_KEY) {
183
- configLines.push(`HINDSIGHT_EMBED_LLM_API_KEY=${env.HINDSIGHT_EMBED_LLM_API_KEY}`);
184
- }
185
- if (env.HINDSIGHT_API_LLM_BASE_URL) {
186
- configLines.push(`HINDSIGHT_API_LLM_BASE_URL=${env.HINDSIGHT_API_LLM_BASE_URL}`);
187
- }
188
- if (env.HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT) {
189
- configLines.push(`HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT=${env.HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT}`);
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
- }
198
- // Add extra settings if they exist
199
- if (extraSettings.length > 0) {
200
- configLines.push('');
201
- configLines.push('# Additional settings');
202
- configLines.push(...extraSettings);
174
+ for (const envVar of envVars) {
175
+ if (env[envVar]) {
176
+ createArgs.push('--env', `${envVar}=${env[envVar]}`);
177
+ }
203
178
  }
204
- // Write to file
205
- await fs.writeFile(embedConfigPath, configLines.join('\n') + '\n', 'utf-8');
206
- console.log(`[Hindsight] Wrote config to ${embedConfigPath}`);
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
+ });
207
209
  }
208
210
  }
package/dist/index.js CHANGED
@@ -31,16 +31,20 @@ const PROVIDER_DETECTION = [
31
31
  { name: 'gemini', keyEnv: 'GEMINI_API_KEY', defaultModel: 'gemini-2.5-flash' },
32
32
  { name: 'groq', keyEnv: 'GROQ_API_KEY', defaultModel: 'openai/gpt-oss-20b' },
33
33
  { name: 'ollama', keyEnv: '', defaultModel: 'llama3.2' },
34
+ { name: 'openai-codex', keyEnv: '', defaultModel: 'gpt-5.2-codex' },
35
+ { name: 'claude-code', keyEnv: '', defaultModel: 'claude-sonnet-4-5-20250929' },
34
36
  ];
35
- function detectLLMConfig() {
37
+ function detectLLMConfig(pluginConfig) {
36
38
  // Override values from HINDSIGHT_API_LLM_* env vars (highest priority)
37
39
  const overrideProvider = process.env.HINDSIGHT_API_LLM_PROVIDER;
38
40
  const overrideModel = process.env.HINDSIGHT_API_LLM_MODEL;
39
41
  const overrideKey = process.env.HINDSIGHT_API_LLM_API_KEY;
40
42
  const overrideBaseUrl = process.env.HINDSIGHT_API_LLM_BASE_URL;
41
- // If provider is explicitly set, use that (with overrides)
43
+ // Priority 1: If provider is explicitly set via env var, use that
42
44
  if (overrideProvider) {
43
- if (!overrideKey && overrideProvider !== 'ollama') {
45
+ // Providers that don't require an API key (use OAuth or local models)
46
+ const noKeyRequired = ['ollama', 'openai-codex', 'claude-code'];
47
+ if (!overrideKey && !noKeyRequired.includes(overrideProvider)) {
44
48
  throw new Error(`HINDSIGHT_API_LLM_PROVIDER is set to "${overrideProvider}" but HINDSIGHT_API_LLM_API_KEY is not set.\n` +
45
49
  `Please set: export HINDSIGHT_API_LLM_API_KEY=your-api-key`);
46
50
  }
@@ -53,11 +57,39 @@ function detectLLMConfig() {
53
57
  source: 'HINDSIGHT_API_LLM_PROVIDER override',
54
58
  };
55
59
  }
56
- // Auto-detect from standard provider env vars
60
+ // Priority 2: Plugin config llmProvider/llmModel
61
+ if (pluginConfig?.llmProvider) {
62
+ const providerInfo = PROVIDER_DETECTION.find(p => p.name === pluginConfig.llmProvider);
63
+ // Resolve API key: llmApiKeyEnv > provider's standard keyEnv
64
+ let apiKey = '';
65
+ if (pluginConfig.llmApiKeyEnv) {
66
+ apiKey = process.env[pluginConfig.llmApiKeyEnv] || '';
67
+ }
68
+ else if (providerInfo?.keyEnv) {
69
+ apiKey = process.env[providerInfo.keyEnv] || '';
70
+ }
71
+ // Providers that don't require an API key (use OAuth or local models)
72
+ const noKeyRequired = ['ollama', 'openai-codex', 'claude-code'];
73
+ if (!apiKey && !noKeyRequired.includes(pluginConfig.llmProvider)) {
74
+ const keySource = pluginConfig.llmApiKeyEnv || providerInfo?.keyEnv || 'unknown';
75
+ throw new Error(`Plugin config llmProvider is set to "${pluginConfig.llmProvider}" but no API key found.\n` +
76
+ `Expected env var: ${keySource}\n` +
77
+ `Set the env var or use llmApiKeyEnv in plugin config to specify a custom env var name.`);
78
+ }
79
+ return {
80
+ provider: pluginConfig.llmProvider,
81
+ apiKey,
82
+ model: pluginConfig.llmModel || overrideModel || providerInfo?.defaultModel,
83
+ baseUrl: overrideBaseUrl,
84
+ source: 'plugin config',
85
+ };
86
+ }
87
+ // Priority 3: Auto-detect from standard provider env vars
57
88
  for (const providerInfo of PROVIDER_DETECTION) {
58
89
  const apiKey = providerInfo.keyEnv ? process.env[providerInfo.keyEnv] : '';
59
- // Skip ollama in auto-detection (must be explicitly requested)
60
- if (providerInfo.name === 'ollama') {
90
+ // Skip providers that don't use API keys in auto-detection (must be explicitly requested)
91
+ const noKeyRequired = ['ollama', 'openai-codex', 'claude-code'];
92
+ if (noKeyRequired.includes(providerInfo.name)) {
61
93
  continue;
62
94
  }
63
95
  if (apiKey) {
@@ -75,9 +107,14 @@ function detectLLMConfig() {
75
107
  `Option 1: Set a standard provider API key (auto-detect):\n` +
76
108
  ` export OPENAI_API_KEY=sk-your-key # Uses gpt-4o-mini\n` +
77
109
  ` 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` +
110
+ ` export GEMINI_API_KEY=your-key # Uses gemini-2.5-flash\n` +
111
+ ` export GROQ_API_KEY=your-key # Uses openai/gpt-oss-20b\n\n` +
112
+ `Option 2: Use Codex or Claude Code (no API key needed):\n` +
113
+ ` export HINDSIGHT_API_LLM_PROVIDER=openai-codex # Requires 'codex auth login'\n` +
114
+ ` export HINDSIGHT_API_LLM_PROVIDER=claude-code # Requires Claude Code CLI\n\n` +
115
+ `Option 3: Set llmProvider in openclaw.json plugin config:\n` +
116
+ ` "llmProvider": "openai", "llmModel": "gpt-4o-mini"\n\n` +
117
+ `Option 4: Override with Hindsight-specific env vars:\n` +
81
118
  ` export HINDSIGHT_API_LLM_PROVIDER=openai\n` +
82
119
  ` export HINDSIGHT_API_LLM_MODEL=gpt-4o-mini\n` +
83
120
  ` export HINDSIGHT_API_LLM_API_KEY=sk-your-key\n` +
@@ -92,14 +129,21 @@ function getPluginConfig(api) {
92
129
  embedPort: config.embedPort || 0,
93
130
  daemonIdleTimeout: config.daemonIdleTimeout !== undefined ? config.daemonIdleTimeout : 0,
94
131
  embedVersion: config.embedVersion || 'latest',
132
+ embedPackagePath: config.embedPackagePath,
133
+ llmProvider: config.llmProvider,
134
+ llmModel: config.llmModel,
135
+ llmApiKeyEnv: config.llmApiKeyEnv,
95
136
  };
96
137
  }
97
138
  export default function (api) {
98
139
  try {
99
140
  console.log('[Hindsight] Plugin loading...');
100
- // Detect LLM configuration from environment
141
+ // Get plugin config first (needed for LLM detection)
142
+ console.log('[Hindsight] Getting plugin config...');
143
+ const pluginConfig = getPluginConfig(api);
144
+ // Detect LLM configuration (env vars > plugin config > auto-detect)
101
145
  console.log('[Hindsight] Detecting LLM config...');
102
- const llmConfig = detectLLMConfig();
146
+ const llmConfig = detectLLMConfig(pluginConfig);
103
147
  const baseUrlInfo = llmConfig.baseUrl ? `, base URL: ${llmConfig.baseUrl}` : '';
104
148
  const modelInfo = llmConfig.model || 'default';
105
149
  if (llmConfig.provider === 'ollama') {
@@ -108,28 +152,26 @@ export default function (api) {
108
152
  else {
109
153
  console.log(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${modelInfo} (${llmConfig.source}${baseUrlInfo})`);
110
154
  }
111
- console.log('[Hindsight] Getting plugin config...');
112
- const pluginConfig = getPluginConfig(api);
113
155
  if (pluginConfig.bankMission) {
114
156
  console.log(`[Hindsight] Custom bank mission configured: "${pluginConfig.bankMission.substring(0, 50)}..."`);
115
157
  }
116
158
  console.log(`[Hindsight] Daemon idle timeout: ${pluginConfig.daemonIdleTimeout}s (0 = never timeout)`);
117
- // Determine port
118
- const port = pluginConfig.embedPort || Math.floor(Math.random() * 10000) + 10000;
119
- console.log(`[Hindsight] Port: ${port}`);
159
+ // Get API port from config (default: 9077)
160
+ const apiPort = pluginConfig.apiPort || 9077;
161
+ console.log(`[Hindsight] API Port: ${apiPort}`);
120
162
  // Initialize in background (non-blocking)
121
163
  console.log('[Hindsight] Starting initialization in background...');
122
164
  initPromise = (async () => {
123
165
  try {
124
166
  // Initialize embed manager
125
167
  console.log('[Hindsight] Creating HindsightEmbedManager...');
126
- embedManager = new HindsightEmbedManager(port, llmConfig.provider, llmConfig.apiKey, llmConfig.model, llmConfig.baseUrl, pluginConfig.daemonIdleTimeout, pluginConfig.embedVersion);
168
+ embedManager = new HindsightEmbedManager(apiPort, llmConfig.provider, llmConfig.apiKey, llmConfig.model, llmConfig.baseUrl, pluginConfig.daemonIdleTimeout, pluginConfig.embedVersion, pluginConfig.embedPackagePath);
127
169
  // Start the embedded server
128
170
  console.log('[Hindsight] Starting embedded server...');
129
171
  await embedManager.start();
130
172
  // Initialize client
131
173
  console.log('[Hindsight] Creating HindsightClient...');
132
- client = new HindsightClient(llmConfig.provider, llmConfig.apiKey, llmConfig.model, pluginConfig.embedVersion);
174
+ client = new HindsightClient(llmConfig.provider, llmConfig.apiKey, llmConfig.model, pluginConfig.embedVersion, pluginConfig.embedPackagePath);
133
175
  // Use openclaw bank
134
176
  console.log(`[Hindsight] Using bank: ${BANK_NAME}`);
135
177
  client.setBankId(BANK_NAME);
@@ -179,12 +221,12 @@ export default function (api) {
179
221
  // Reinitialize if needed (fresh start or recovery from dead daemon)
180
222
  if (!isInitialized) {
181
223
  console.log('[Hindsight] Reinitializing daemon...');
182
- const llmConfig = detectLLMConfig();
183
224
  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);
225
+ const llmConfig = detectLLMConfig(pluginConfig);
226
+ const apiPort = pluginConfig.apiPort || 9077;
227
+ embedManager = new HindsightEmbedManager(apiPort, llmConfig.provider, llmConfig.apiKey, llmConfig.model, llmConfig.baseUrl, pluginConfig.daemonIdleTimeout, pluginConfig.embedVersion, pluginConfig.embedPackagePath);
186
228
  await embedManager.start();
187
- client = new HindsightClient(llmConfig.provider, llmConfig.apiKey, llmConfig.model, pluginConfig.embedVersion);
229
+ client = new HindsightClient(llmConfig.provider, llmConfig.apiKey, llmConfig.model, pluginConfig.embedVersion, pluginConfig.embedPackagePath);
188
230
  client.setBankId(BANK_NAME);
189
231
  if (pluginConfig.bankMission) {
190
232
  await client.setBankMission(pluginConfig.bankMission);
package/dist/types.d.ts CHANGED
@@ -29,6 +29,11 @@ export interface PluginConfig {
29
29
  embedPort?: number;
30
30
  daemonIdleTimeout?: number;
31
31
  embedVersion?: string;
32
+ embedPackagePath?: string;
33
+ llmProvider?: string;
34
+ llmModel?: string;
35
+ llmApiKeyEnv?: string;
36
+ apiPort?: number;
32
37
  }
33
38
  export interface ServiceConfig {
34
39
  id: string;
@@ -24,6 +24,28 @@
24
24
  "type": "string",
25
25
  "description": "hindsight-embed version to use (e.g. 'latest', '0.4.2', or empty for latest)",
26
26
  "default": "latest"
27
+ },
28
+ "llmProvider": {
29
+ "type": "string",
30
+ "description": "LLM provider for Hindsight memory (e.g. 'openai', 'anthropic', 'gemini', 'groq', 'ollama', 'openai-codex', 'claude-code'). Takes priority over auto-detection but not over HINDSIGHT_API_LLM_PROVIDER env var.",
31
+ "enum": ["openai", "anthropic", "gemini", "groq", "ollama", "openai-codex", "claude-code"]
32
+ },
33
+ "llmModel": {
34
+ "type": "string",
35
+ "description": "LLM model to use (e.g. 'gpt-4o-mini', 'claude-3-5-haiku-20241022'). Used with llmProvider."
36
+ },
37
+ "llmApiKeyEnv": {
38
+ "type": "string",
39
+ "description": "Name of the env var holding the API key (e.g. 'MY_CUSTOM_KEY'). If not set, uses the standard env var for the chosen provider."
40
+ },
41
+ "embedPackagePath": {
42
+ "type": "string",
43
+ "description": "Local path to hindsight package for development (e.g. '/path/to/hindsight'). When set, uses 'uv run --directory <path>' instead of 'uvx hindsight-embed@latest'."
44
+ },
45
+ "apiPort": {
46
+ "type": "number",
47
+ "description": "Port for the openclaw profile daemon (default: 9077)",
48
+ "default": 9077
27
49
  }
28
50
  },
29
51
  "additionalProperties": false
@@ -44,6 +66,26 @@
44
66
  "embedVersion": {
45
67
  "label": "Hindsight Embed Version",
46
68
  "placeholder": "latest (or pin to specific version like 0.4.2)"
69
+ },
70
+ "llmProvider": {
71
+ "label": "LLM Provider",
72
+ "placeholder": "e.g. openai, anthropic, gemini, groq"
73
+ },
74
+ "llmModel": {
75
+ "label": "LLM Model",
76
+ "placeholder": "e.g. gpt-4o-mini, claude-3-5-haiku-20241022"
77
+ },
78
+ "llmApiKeyEnv": {
79
+ "label": "API Key Env Var",
80
+ "placeholder": "e.g. MY_CUSTOM_API_KEY (optional)"
81
+ },
82
+ "embedPackagePath": {
83
+ "label": "Local Package Path (Dev)",
84
+ "placeholder": "/path/to/hindsight (for local development)"
85
+ },
86
+ "apiPort": {
87
+ "label": "API Port",
88
+ "placeholder": "9077 (default)"
47
89
  }
48
90
  }
49
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectorize-io/hindsight-openclaw",
3
- "version": "0.4.7",
3
+ "version": "0.4.8",
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",