feat: moltbot integration (#216)
* feat: moltbot integration * fixes * fixes
This commit is contained in:
parent
c16ccc2c22
commit
12e9a3d305
23 changed files with 3206 additions and 1 deletions
59
.github/workflows/release.yml
vendored
59
.github/workflows/release.yml
vendored
|
|
@ -139,6 +139,55 @@ jobs:
|
|||
path: hindsight-clients/typescript/*.tgz
|
||||
retention-days: 1
|
||||
|
||||
release-moltbot-integration:
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./hindsight-integrations/moltbot
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
working-directory: ./hindsight-integrations/moltbot
|
||||
run: npm run build
|
||||
|
||||
- name: Publish to npm
|
||||
working-directory: ./hindsight-integrations/moltbot
|
||||
run: |
|
||||
set +e
|
||||
OUTPUT=$(npm publish --access public 2>&1)
|
||||
EXIT_CODE=$?
|
||||
echo "$OUTPUT"
|
||||
if [ $EXIT_CODE -ne 0 ]; then
|
||||
if echo "$OUTPUT" | grep -q "cannot publish over"; then
|
||||
echo "Package version already published, skipping..."
|
||||
exit 0
|
||||
fi
|
||||
exit $EXIT_CODE
|
||||
fi
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Pack for GitHub release
|
||||
working-directory: ./hindsight-integrations/moltbot
|
||||
run: npm pack
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: moltbot-integration
|
||||
path: hindsight-integrations/moltbot/*.tgz
|
||||
retention-days: 1
|
||||
|
||||
release-control-plane:
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm
|
||||
|
|
@ -366,7 +415,7 @@ jobs:
|
|||
|
||||
create-github-release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [release-python-packages, release-typescript-client, release-control-plane, release-rust-cli, release-docker-images, release-helm-chart]
|
||||
needs: [release-python-packages, release-typescript-client, release-moltbot-integration, release-control-plane, release-rust-cli, release-docker-images, release-helm-chart]
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
|
|
@ -389,6 +438,12 @@ jobs:
|
|||
name: typescript-client
|
||||
path: ./artifacts/typescript-client
|
||||
|
||||
- name: Download Moltbot Integration
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: moltbot-integration
|
||||
path: ./artifacts/moltbot-integration
|
||||
|
||||
- name: Download Control Plane
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
|
@ -430,6 +485,8 @@ jobs:
|
|||
cp artifacts/python-packages/hindsight-embed/dist/* release-assets/ || true
|
||||
# TypeScript client
|
||||
cp artifacts/typescript-client/*.tgz release-assets/ || true
|
||||
# Moltbot Integration
|
||||
cp artifacts/moltbot-integration/*.tgz release-assets/ || true
|
||||
# Control Plane
|
||||
cp artifacts/control-plane/*.tgz release-assets/ || true
|
||||
# Rust CLI binaries
|
||||
|
|
|
|||
23
.github/workflows/test.yml
vendored
23
.github/workflows/test.yml
vendored
|
|
@ -82,6 +82,29 @@ jobs:
|
|||
- name: Build TypeScript client
|
||||
run: npm run build --workspace=hindsight-clients/typescript
|
||||
|
||||
build-moltbot-integration:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./hindsight-integrations/moltbot
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
working-directory: ./hindsight-integrations/moltbot
|
||||
run: npm test
|
||||
|
||||
- name: Build
|
||||
working-directory: ./hindsight-integrations/moltbot
|
||||
run: npm run build
|
||||
|
||||
build-control-plane:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
|
|
|||
257
hindsight-docs/docs/sdks/integrations/moltbot.md
Normal file
257
hindsight-docs/docs/sdks/integrations/moltbot.md
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Moltbot (Clawdbot)
|
||||
|
||||
Biomimetic long-term memory for [Moltbot](https://molt.bot) using [Hindsight](https://vectorize.io/hindsight). Automatically captures conversations and intelligently recalls relevant context.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Install the plugin
|
||||
npm install -g @vectorize-io/hindsight-moltbot-plugin
|
||||
|
||||
# 2. Configure your LLM provider
|
||||
export OPENAI_API_KEY="sk-your-key"
|
||||
clawdbot config set 'agents.defaults.models."openai/gpt-4o-mini"' '{}'
|
||||
|
||||
# 3. Enable the plugin
|
||||
clawdbot plugins enable hindsight-memory
|
||||
|
||||
# 4. Start Moltbot
|
||||
clawdbot gateway
|
||||
```
|
||||
|
||||
That's it! The plugin will automatically start capturing and recalling memories.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Auto-Capture (Hooks)
|
||||
Every conversation is **automatically stored** after each turn:
|
||||
- Extracts facts, entities, and relationships
|
||||
- Processes in background (non-blocking)
|
||||
- Stores in PostgreSQL via embedded `hindsight-api`
|
||||
|
||||
### Auto-Recall (Before Agent Start)
|
||||
Before each agent response, relevant memories are **automatically injected**:
|
||||
- Relevant memories retrieved (up to 1024 tokens)
|
||||
- Injected into context with `<hindsight-context>` tags
|
||||
- Agent seamlessly uses past context
|
||||
|
||||
## Understanding Moltbot Concepts
|
||||
|
||||
### Plugins
|
||||
Extensions that add functionality to Moltbot. This Hindsight plugin:
|
||||
- Runs a background service (manages `hindsight-embed` daemon)
|
||||
- Registers hooks (automatic event handlers)
|
||||
|
||||
### Hooks
|
||||
Automatic event handlers that run without agent involvement:
|
||||
- **`before_agent_start`**: Auto-recall - injects memories before agent processes message
|
||||
- **`agent_end`**: Auto-capture - stores conversation after agent responds
|
||||
|
||||
Think of hooks as "forced automation" - they always run.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Moltbot Gateway │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────┐ │
|
||||
│ │ Hindsight Plugin │ │
|
||||
│ │ │ │
|
||||
│ │ • Service: Manages daemon │ │
|
||||
│ │ • Hook: before_agent_start │ │
|
||||
│ │ → Auto-recall (1024 tokens) │ │
|
||||
│ │ • Hook: agent_end │ │
|
||||
│ │ → Auto-capture │ │
|
||||
│ └───────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
↓
|
||||
uvx hindsight-embed
|
||||
• Daemon on port 8889
|
||||
• PostgreSQL (pg0)
|
||||
• Fact extraction
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Node.js** 22+
|
||||
- **Moltbot** (Clawdbot) with plugin support
|
||||
- **uv/uvx** for running `hindsight-embed`
|
||||
- **LLM API key** (OpenAI, Anthropic, etc.)
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# 1. Install the plugin
|
||||
npm install -g @vectorize-io/hindsight-moltbot-plugin
|
||||
|
||||
# 2. Configure your LLM provider
|
||||
export OPENAI_API_KEY="sk-your-key"
|
||||
clawdbot config set 'agents.defaults.models."openai/gpt-4o-mini"' '{}'
|
||||
|
||||
# 3. Enable the plugin
|
||||
clawdbot plugins enable hindsight-memory
|
||||
|
||||
# 4. Start Moltbot
|
||||
clawdbot gateway
|
||||
```
|
||||
|
||||
On first start, `uvx` will automatically download `hindsight-embed` (no manual installation needed).
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
Optional settings in `~/.clawdbot/clawdbot.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"hindsight-memory": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"daemonIdleTimeout": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `daemonIdleTimeout` (number, default: `0`) - Seconds before daemon shuts down from inactivity (0 = never)
|
||||
- `embedPort` (number, default: auto) - Port for embedded server
|
||||
- `bankMission` (string, default: none) - Custom context for the memory bank
|
||||
|
||||
## Supported LLM Providers
|
||||
|
||||
The plugin auto-detects your configured provider and API key:
|
||||
|
||||
| Provider | Environment Variable | Model Example |
|
||||
|----------|---------------------|---------------|
|
||||
| OpenAI | `OPENAI_API_KEY` | `openai/gpt-4o-mini` |
|
||||
| Anthropic | `ANTHROPIC_API_KEY` | `anthropic/claude-sonnet-4` |
|
||||
| Gemini | `GEMINI_API_KEY` | `gemini/gemini-2.0-flash-exp` |
|
||||
| Groq | `GROQ_API_KEY` | `groq/llama-3.3-70b` |
|
||||
| Ollama | None needed | `ollama/llama3` |
|
||||
|
||||
Configure with:
|
||||
```bash
|
||||
export OPENAI_API_KEY="sk-your-key"
|
||||
clawdbot config set 'agents.defaults.models."openai/gpt-4o-mini"' '{}'
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
**Check if plugin is loaded:**
|
||||
```bash
|
||||
clawdbot plugins list | grep hindsight
|
||||
# Should show: ✓ enabled │ Hindsight Memory │ ...
|
||||
```
|
||||
|
||||
**Test auto-recall:**
|
||||
Send a message on any Moltbot channel (Telegram, Slack, etc.):
|
||||
```
|
||||
User: My name is John and I love pizza
|
||||
Bot: Got it! I'll remember that.
|
||||
|
||||
User: What do I like to eat?
|
||||
Bot: You love pizza! # ← Used auto-recall
|
||||
```
|
||||
|
||||
**View daemon logs:**
|
||||
```bash
|
||||
tail -f ~/.hindsight/daemon.log
|
||||
```
|
||||
|
||||
**Check memories in database:**
|
||||
```bash
|
||||
uvx hindsight-embed memory recall moltbot "pizza" --output json
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Plugin not loading?**
|
||||
```bash
|
||||
# Check plugin installation
|
||||
npm list -g @vectorize-io/hindsight-moltbot-plugin
|
||||
|
||||
# Reinstall if needed
|
||||
npm install -g @vectorize-io/hindsight-moltbot-plugin
|
||||
clawdbot plugins enable hindsight-memory
|
||||
```
|
||||
|
||||
**Daemon not starting?**
|
||||
```bash
|
||||
# Check daemon status
|
||||
uvx hindsight-embed daemon status
|
||||
|
||||
# Manually start
|
||||
uvx hindsight-embed daemon start
|
||||
|
||||
# View logs
|
||||
tail -f ~/.hindsight/daemon.log
|
||||
```
|
||||
|
||||
**No API key error?**
|
||||
```bash
|
||||
# Set in shell profile
|
||||
echo 'export OPENAI_API_KEY="sk-your-key"' >> ~/.zshrc
|
||||
source ~/.zshrc
|
||||
|
||||
# Verify
|
||||
echo $OPENAI_API_KEY
|
||||
```
|
||||
|
||||
**Memories not being stored?**
|
||||
```bash
|
||||
# Check gateway logs for auto-capture
|
||||
tail -f /tmp/clawdbot/clawdbot-*.log | grep Hindsight
|
||||
|
||||
# Should see:
|
||||
# [Hindsight Hook] agent_end triggered
|
||||
# [Hindsight] Retained X messages for session ...
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Clone repo
|
||||
git clone https://github.com/vectorize-io/hindsight.git
|
||||
cd hindsight/hindsight-integrations/moltbot
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build
|
||||
npm run build
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
|
||||
# Install locally
|
||||
npm run build && ./install.sh
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Node.js** 22+
|
||||
- **Moltbot** (Clawdbot) with plugin support
|
||||
- **uv/uvx** for running `hindsight-embed`
|
||||
- **LLM API key** (OpenAI, Anthropic, etc.)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Links
|
||||
|
||||
- [Hindsight Documentation](https://vectorize.io/hindsight)
|
||||
- [Moltbot Documentation](https://docs.molt.bot)
|
||||
- [GitHub Repository](https://github.com/vectorize-io/hindsight)
|
||||
|
|
@ -187,6 +187,11 @@ const sidebars: SidebarsConfig = {
|
|||
id: 'sdks/integrations/litellm',
|
||||
label: 'LiteLLM',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'sdks/integrations/moltbot',
|
||||
label: 'Moltbot',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'sdks/integrations/skills',
|
||||
|
|
|
|||
4
hindsight-integrations/moltbot/.gitignore
vendored
Normal file
4
hindsight-integrations/moltbot/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
.DS_Store
|
||||
38
hindsight-integrations/moltbot/README.md
Normal file
38
hindsight-integrations/moltbot/README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Hindsight Memory Plugin for Moltbot
|
||||
|
||||
Biomimetic long-term memory for [Moltbot](https://molt.bot) using [Hindsight](https://vectorize.io/hindsight). Automatically captures conversations and intelligently recalls relevant context.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Install the plugin
|
||||
npm install -g @vectorize-io/hindsight-moltbot-plugin
|
||||
|
||||
# 2. Configure your LLM provider
|
||||
export OPENAI_API_KEY="sk-your-key"
|
||||
clawdbot config set 'agents.defaults.models."openai/gpt-4o-mini"' '{}'
|
||||
|
||||
# 3. Enable the plugin
|
||||
clawdbot plugins enable hindsight-memory
|
||||
|
||||
# 4. Start Moltbot
|
||||
clawdbot gateway
|
||||
```
|
||||
|
||||
That's it! The plugin will automatically start capturing and recalling memories.
|
||||
|
||||
## Documentation
|
||||
|
||||
For full documentation, configuration options, troubleshooting, and development guide, see:
|
||||
|
||||
**[Moltbot Integration Documentation](https://vectorize.io/hindsight/sdks/integrations/moltbot)**
|
||||
|
||||
## Links
|
||||
|
||||
- [Hindsight Documentation](https://vectorize.io/hindsight)
|
||||
- [Moltbot Documentation](https://docs.molt.bot)
|
||||
- [GitHub Repository](https://github.com/vectorize-io/hindsight)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
33
hindsight-integrations/moltbot/clawdbot.plugin.json
Normal file
33
hindsight-integrations/moltbot/clawdbot.plugin.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"id": "hindsight-memory",
|
||||
"name": "Hindsight Memory",
|
||||
"kind": "memory",
|
||||
"moltbot": {
|
||||
"skills": ["skills"]
|
||||
},
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bankMission": {
|
||||
"type": "string",
|
||||
"description": "Custom mission/context for the memory bank (overrides default)"
|
||||
},
|
||||
"embedPort": {
|
||||
"type": "number",
|
||||
"description": "Port for hindsight-embed server (auto-assigned if not specified)",
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"uiHints": {
|
||||
"bankMission": {
|
||||
"label": "Bank Mission",
|
||||
"placeholder": "Custom context for what this agent does..."
|
||||
},
|
||||
"embedPort": {
|
||||
"label": "Embed Server Port",
|
||||
"placeholder": "0 (auto-assign)"
|
||||
}
|
||||
}
|
||||
}
|
||||
25
hindsight-integrations/moltbot/hooks/retain-messages/HOOK.md
Normal file
25
hindsight-integrations/moltbot/hooks/retain-messages/HOOK.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
name: hindsight-retain-messages
|
||||
description: Automatically retains messages to Hindsight long-term memory
|
||||
events:
|
||||
- agent_end
|
||||
metadata:
|
||||
moltbot:
|
||||
emoji: 🧠
|
||||
---
|
||||
|
||||
# Hindsight Message Retention
|
||||
|
||||
This hook automatically retains conversation messages to Hindsight's long-term memory.
|
||||
|
||||
## When It Runs
|
||||
|
||||
- On `agent_end`: After each agent turn completes
|
||||
|
||||
## What It Does
|
||||
|
||||
1. Captures the current session messages
|
||||
2. Formats them into a conversation transcript
|
||||
3. Calls Hindsight's retain API with the session_id as document_id
|
||||
4. Queues for background processing (async)
|
||||
5. Extracts facts, entities, and relationships from the conversation
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// Handler for auto-retaining messages to Hindsight
|
||||
const handler = async (event) => {
|
||||
console.log(`[Hindsight Hook] Received event: ${event.type}`);
|
||||
|
||||
// Only process agent_end events (after each agent turn)
|
||||
if (event.type !== 'agent_end') {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Hindsight Hook] Processing retention after agent turn...');
|
||||
|
||||
try {
|
||||
// Get client from global (set by main plugin)
|
||||
const clientGlobal = global.__hindsightClient;
|
||||
if (!clientGlobal) {
|
||||
console.warn('[Hindsight] Client global not found, skipping retain');
|
||||
return;
|
||||
}
|
||||
|
||||
const client = clientGlobal.getClient();
|
||||
if (!client) {
|
||||
console.warn('[Hindsight] Client not initialized, skipping retain');
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract session information
|
||||
const { sessionId, sessionKey } = event.context || {};
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get messages from the event context
|
||||
const sessionEntry = event.context?.sessionEntry;
|
||||
if (!sessionEntry || !sessionEntry.messages || sessionEntry.messages.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Format messages into a transcript
|
||||
const transcript = sessionEntry.messages
|
||||
.map((msg) => {
|
||||
const role = msg.role || 'unknown';
|
||||
const content = msg.content || '';
|
||||
return `${role}: ${content}`;
|
||||
})
|
||||
.join('\n\n');
|
||||
|
||||
if (!transcript.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retain to Hindsight with session_id as document_id
|
||||
await client.retain({
|
||||
content: transcript,
|
||||
document_id: sessionId,
|
||||
metadata: {
|
||||
session_key: sessionKey,
|
||||
retained_at: new Date().toISOString(),
|
||||
message_count: sessionEntry.messages.length,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`[Hindsight] Retained ${sessionEntry.messages.length} messages for session ${sessionId}`);
|
||||
} catch (error) {
|
||||
console.error('[Hindsight] Error retaining messages:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// Handler for auto-retaining messages to Hindsight
|
||||
|
||||
import type { HookHandler } from 'moltbot/plugin-sdk';
|
||||
|
||||
const handler: HookHandler = async (event) => {
|
||||
// Only process tool_result_persist and command:new events
|
||||
if (
|
||||
event.type !== 'tool_result_persist' &&
|
||||
!(event.type === 'command' && event.action === 'new')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get client from global (set by main plugin)
|
||||
const clientGlobal = (global as any).__hindsightClient;
|
||||
if (!clientGlobal) {
|
||||
console.warn('[Hindsight] Client global not found, skipping retain');
|
||||
return;
|
||||
}
|
||||
|
||||
const client = clientGlobal.getClient();
|
||||
if (!client) {
|
||||
console.warn('[Hindsight] Client not initialized, skipping retain');
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract session information
|
||||
const { sessionId, sessionKey } = event.context || {};
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get messages from the event context
|
||||
// The messages are in event.context.sessionEntry or similar
|
||||
const sessionEntry = event.context?.sessionEntry;
|
||||
if (!sessionEntry || !sessionEntry.messages || sessionEntry.messages.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Format messages into a transcript
|
||||
const transcript = sessionEntry.messages
|
||||
.map((msg: any) => {
|
||||
const role = msg.role || 'unknown';
|
||||
const content = msg.content || '';
|
||||
return `${role}: ${content}`;
|
||||
})
|
||||
.join('\n\n');
|
||||
|
||||
if (!transcript.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retain to Hindsight with session_id as document_id
|
||||
await client.retain({
|
||||
content: transcript,
|
||||
document_id: sessionId,
|
||||
metadata: {
|
||||
session_key: sessionKey,
|
||||
retained_at: new Date().toISOString(),
|
||||
message_count: sessionEntry.messages.length,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`[Hindsight] Retained ${sessionEntry.messages.length} messages for session ${sessionId}`);
|
||||
} catch (error) {
|
||||
console.error('[Hindsight] Error retaining messages:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
49
hindsight-integrations/moltbot/install.sh
Executable file
49
hindsight-integrations/moltbot/install.sh
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🚀 Installing Hindsight Memory Plugin for Moltbot..."
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
INSTALL_DIR="$HOME/.clawdbot/extensions/hindsight-memory"
|
||||
|
||||
# Check Node version
|
||||
if ! command -v node &> /dev/null; then
|
||||
echo "❌ Node.js not found. Please install Node.js 22+"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the plugin
|
||||
echo "📦 Building plugin..."
|
||||
cd "$SCRIPT_DIR"
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# Deploy to Clawdbot extensions
|
||||
echo "📂 Deploying to $INSTALL_DIR..."
|
||||
rm -rf "$INSTALL_DIR"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
|
||||
# Copy files
|
||||
cp -r dist package.json clawdbot.plugin.json hooks README.md "$INSTALL_DIR/"
|
||||
|
||||
# Install dependencies in deployed location
|
||||
echo "📥 Installing dependencies..."
|
||||
cd "$INSTALL_DIR"
|
||||
npm install
|
||||
|
||||
echo ""
|
||||
echo "✅ Hindsight Memory Plugin installed successfully!"
|
||||
echo ""
|
||||
echo "📋 Next steps:"
|
||||
echo ""
|
||||
echo "1. Make sure you have an OpenAI API key set:"
|
||||
echo " export OPENAI_API_KEY=\"sk-your-key-here\""
|
||||
echo ""
|
||||
echo "2. Enable the plugin:"
|
||||
echo " clawdbot plugins enable hindsight-memory"
|
||||
echo ""
|
||||
echo "3. Start Moltbot:"
|
||||
echo " clawdbot start"
|
||||
echo ""
|
||||
echo "On first start, uvx will automatically download hindsight-embed (no manual install needed)"
|
||||
1675
hindsight-integrations/moltbot/package-lock.json
generated
Normal file
1675
hindsight-integrations/moltbot/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
58
hindsight-integrations/moltbot/package.json
Normal file
58
hindsight-integrations/moltbot/package.json
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"name": "@vectorize-io/hindsight-moltbot-plugin",
|
||||
"version": "0.1.0",
|
||||
"description": "Hindsight memory plugin for Moltbot - biomimetic long-term memory with fact extraction",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"clawdbot": {
|
||||
"extensions": [
|
||||
"./dist/index.js"
|
||||
],
|
||||
"hooks": [
|
||||
"hooks/retain-messages"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"moltbot",
|
||||
"clawdbot",
|
||||
"memory",
|
||||
"ai",
|
||||
"agent",
|
||||
"hindsight",
|
||||
"long-term-memory"
|
||||
],
|
||||
"author": "Vectorize <support@vectorize.io>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vectorize-io/hindsight.git",
|
||||
"directory": "hindsight-integrations/moltbot"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"clawdbot.plugin.json",
|
||||
"hooks",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"prepublishOnly": "npm run clean && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@vitest/ui": "^4.0.18",
|
||||
"typescript": "^5.3.0",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
}
|
||||
34
hindsight-integrations/moltbot/skills/hindsight/SKILL.md
Normal file
34
hindsight-integrations/moltbot/skills/hindsight/SKILL.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
name: memory_search
|
||||
description: Search your long-term memory for relevant facts, experiences, and context using semantic and graph-based retrieval
|
||||
user-invocable: false
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
# memory_search
|
||||
|
||||
Search your long-term memory for relevant information. This tool provides multi-strategy retrieval combining:
|
||||
- Semantic search across facts and experiences
|
||||
- BM25 keyword matching
|
||||
- Entity graph traversal
|
||||
- Temporal queries
|
||||
- Cross-encoder reranking
|
||||
|
||||
## Usage
|
||||
|
||||
Call `memory_search` with a natural language query to find relevant memories:
|
||||
|
||||
```
|
||||
memory_search "What does the user prefer for breakfast?"
|
||||
memory_search "When did we discuss the project deadline?"
|
||||
memory_search "Tell me about Paris"
|
||||
```
|
||||
|
||||
## Returns
|
||||
|
||||
Returns a list of relevant memory fragments with:
|
||||
- Content: The actual memory text
|
||||
- Score: Relevance score (0-1)
|
||||
- Metadata: Source document, creation date, entities
|
||||
|
||||
Use the results to inform your responses with context from past conversations.
|
||||
46
hindsight-integrations/moltbot/skills/hindsight/handler.ts
Normal file
46
hindsight-integrations/moltbot/skills/hindsight/handler.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// Handler for memory_search tool
|
||||
// This will be called when the agent invokes memory_search
|
||||
|
||||
import { getClient } from '../../src/index.js';
|
||||
|
||||
export interface ToolContext {
|
||||
query: string;
|
||||
args: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export async function handle(ctx: ToolContext): Promise<string> {
|
||||
try {
|
||||
const { query } = ctx;
|
||||
|
||||
const client = getClient();
|
||||
if (!client) {
|
||||
throw new Error('Hindsight client not initialized');
|
||||
}
|
||||
|
||||
// Call Hindsight recall API
|
||||
const response = await client.recall({
|
||||
query,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
// Format results for the agent
|
||||
if (!response.results || response.results.length === 0) {
|
||||
return 'No relevant memories found for this query.';
|
||||
}
|
||||
|
||||
const formatted = response.results
|
||||
.map((result: any, idx: number) => {
|
||||
const score = result.score ? ` (relevance: ${result.score.toFixed(2)})` : '';
|
||||
const date = result.metadata?.created_at
|
||||
? ` [${new Date(result.metadata.created_at).toLocaleDateString()}]`
|
||||
: '';
|
||||
return `${idx + 1}. ${result.content}${score}${date}`;
|
||||
})
|
||||
.join('\n\n');
|
||||
|
||||
return `Found ${response.results.length} relevant memories:\n\n${formatted}`;
|
||||
} catch (error) {
|
||||
console.error('[Hindsight] memory_search error:', error);
|
||||
return `Error searching memories: ${error instanceof Error ? error.message : String(error)}`;
|
||||
}
|
||||
}
|
||||
23
hindsight-integrations/moltbot/src/client.test.ts
Normal file
23
hindsight-integrations/moltbot/src/client.test.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { HindsightClient } from './client.js';
|
||||
|
||||
describe('HindsightClient', () => {
|
||||
it('should create instance with provider and API key', () => {
|
||||
const client = new HindsightClient('openai', 'test-key', 'gpt-4');
|
||||
expect(client).toBeInstanceOf(HindsightClient);
|
||||
});
|
||||
|
||||
it('should set bank ID', () => {
|
||||
const client = new HindsightClient('openai', 'test-key');
|
||||
client.setBankId('test-bank');
|
||||
// No error thrown means success
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle content escaping for single quotes', () => {
|
||||
const client = new HindsightClient('openai', 'test-key');
|
||||
// This test validates the client is instantiated correctly
|
||||
// Actual CLI calls would require mocking
|
||||
expect(client).toBeDefined();
|
||||
});
|
||||
});
|
||||
92
hindsight-integrations/moltbot/src/client.ts
Normal file
92
hindsight-integrations/moltbot/src/client.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import fetch from 'node-fetch';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import type {
|
||||
RetainRequest,
|
||||
RetainResponse,
|
||||
RecallRequest,
|
||||
RecallResponse,
|
||||
} from './types.js';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export class HindsightClient {
|
||||
private bankId: string = 'default'; // Always use default bank
|
||||
private llmProvider: string;
|
||||
private llmApiKey: string;
|
||||
private llmModel?: string;
|
||||
|
||||
constructor(llmProvider: string, llmApiKey: string, llmModel?: string) {
|
||||
this.llmProvider = llmProvider;
|
||||
this.llmApiKey = llmApiKey;
|
||||
this.llmModel = llmModel;
|
||||
}
|
||||
|
||||
setBankId(bankId: string): void {
|
||||
this.bankId = bankId;
|
||||
}
|
||||
|
||||
private getEnv(): Record<string, string> {
|
||||
const env: Record<string, string> = {
|
||||
...process.env,
|
||||
HINDSIGHT_EMBED_LLM_PROVIDER: this.llmProvider,
|
||||
HINDSIGHT_EMBED_LLM_API_KEY: this.llmApiKey,
|
||||
};
|
||||
|
||||
if (this.llmModel) {
|
||||
env.HINDSIGHT_EMBED_LLM_MODEL = this.llmModel;
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
async retain(request: RetainRequest): Promise<RetainResponse> {
|
||||
const content = request.content.replace(/'/g, "'\\''"); // Escape single quotes
|
||||
const docId = request.document_id || 'conversation';
|
||||
|
||||
const cmd = `uvx hindsight-embed memory retain ${this.bankId} '${content}' --doc-id '${docId}' --async`;
|
||||
|
||||
try {
|
||||
const { stdout } = await execAsync(cmd, { env: this.getEnv() });
|
||||
console.log(`[Hindsight] Retained (async): ${stdout.trim()}`);
|
||||
|
||||
// Return a simple response
|
||||
return {
|
||||
message: 'Memory queued for background processing',
|
||||
document_id: docId,
|
||||
memory_unit_ids: [],
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to retain memory: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async recall(request: RecallRequest): Promise<RecallResponse> {
|
||||
const query = request.query.replace(/'/g, "'\\''"); // Escape single quotes
|
||||
const maxTokens = request.max_tokens || 1024;
|
||||
|
||||
const cmd = `uvx hindsight-embed memory recall ${this.bankId} '${query}' --output json --max-tokens ${maxTokens}`;
|
||||
|
||||
try {
|
||||
const { stdout } = await execAsync(cmd, { env: this.getEnv() });
|
||||
|
||||
// Parse JSON output - returns { entities: {...}, results: [...] }
|
||||
const response = JSON.parse(stdout);
|
||||
const results = response.results || [];
|
||||
|
||||
return {
|
||||
results: results.map((r: any) => ({
|
||||
content: r.text || r.content || '',
|
||||
score: 1.0, // CLI doesn't return scores
|
||||
metadata: {
|
||||
document_id: r.document_id,
|
||||
chunk_id: r.chunk_id,
|
||||
...r.metadata,
|
||||
},
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to recall memories: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
143
hindsight-integrations/moltbot/src/embed-manager.ts
Normal file
143
hindsight-integrations/moltbot/src/embed-manager.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import { spawn, ChildProcess } from 'child_process';
|
||||
import { promises as fs } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
export class HindsightEmbedManager {
|
||||
private process: ChildProcess | null = null;
|
||||
private port: number;
|
||||
private baseUrl: string;
|
||||
private embedDir: string;
|
||||
private llmProvider: string;
|
||||
private llmApiKey: string;
|
||||
private llmModel?: string;
|
||||
private daemonIdleTimeout: number;
|
||||
|
||||
constructor(
|
||||
port: number,
|
||||
llmProvider: string,
|
||||
llmApiKey: string,
|
||||
llmModel?: string,
|
||||
daemonIdleTimeout: number = 0 // Default: never timeout
|
||||
) {
|
||||
this.port = 8889; // hindsight-embed uses fixed port 8889
|
||||
this.baseUrl = `http://127.0.0.1:8889`;
|
||||
this.embedDir = join(homedir(), '.clawdbot', 'hindsight-embed');
|
||||
this.llmProvider = llmProvider;
|
||||
this.llmApiKey = llmApiKey;
|
||||
this.llmModel = llmModel;
|
||||
this.daemonIdleTimeout = daemonIdleTimeout;
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
console.log(`[Hindsight] Starting hindsight-embed daemon...`);
|
||||
|
||||
// Build environment variables
|
||||
const env: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
HINDSIGHT_EMBED_LLM_PROVIDER: this.llmProvider,
|
||||
HINDSIGHT_EMBED_LLM_API_KEY: this.llmApiKey,
|
||||
HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT: this.daemonIdleTimeout.toString(),
|
||||
};
|
||||
|
||||
if (this.llmModel) {
|
||||
env['HINDSIGHT_EMBED_LLM_MODEL'] = this.llmModel;
|
||||
}
|
||||
|
||||
// Start hindsight-embed daemon (it manages itself)
|
||||
const startDaemon = spawn(
|
||||
'uvx',
|
||||
['hindsight-embed', 'daemon', 'start'],
|
||||
{
|
||||
env,
|
||||
stdio: 'pipe',
|
||||
}
|
||||
);
|
||||
|
||||
// Collect output
|
||||
let output = '';
|
||||
startDaemon.stdout?.on('data', (data) => {
|
||||
const text = data.toString();
|
||||
output += text;
|
||||
console.log(`[Hindsight] ${text.trim()}`);
|
||||
});
|
||||
|
||||
startDaemon.stderr?.on('data', (data) => {
|
||||
const text = data.toString();
|
||||
output += text;
|
||||
console.error(`[Hindsight] ${text.trim()}`);
|
||||
});
|
||||
|
||||
// Wait for daemon start command to complete
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
startDaemon.on('exit', (code) => {
|
||||
if (code === 0) {
|
||||
console.log('[Hindsight] Daemon start command completed');
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Daemon start failed with code ${code}: ${output}`));
|
||||
}
|
||||
});
|
||||
|
||||
startDaemon.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
// Wait for server to be ready
|
||||
await this.waitForReady();
|
||||
console.log('[Hindsight] Daemon is ready');
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
console.log('[Hindsight] Stopping hindsight-embed daemon...');
|
||||
|
||||
const stopDaemon = spawn('uvx', ['hindsight-embed', 'daemon', 'stop'], {
|
||||
stdio: 'pipe',
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
stopDaemon.on('exit', () => {
|
||||
console.log('[Hindsight] Daemon stopped');
|
||||
resolve();
|
||||
});
|
||||
|
||||
stopDaemon.on('error', (error) => {
|
||||
console.error('[Hindsight] Error stopping daemon:', error);
|
||||
resolve(); // Resolve anyway
|
||||
});
|
||||
|
||||
// Timeout after 5 seconds
|
||||
setTimeout(() => {
|
||||
console.log('[Hindsight] Daemon stop timeout');
|
||||
resolve();
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
private async waitForReady(maxAttempts = 30): Promise<void> {
|
||||
console.log('[Hindsight] Waiting for daemon to be ready...');
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/health`);
|
||||
if (response.ok) {
|
||||
console.log('[Hindsight] Daemon health check passed');
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Not ready yet
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
throw new Error('Hindsight daemon failed to become ready within 30 seconds');
|
||||
}
|
||||
|
||||
getBaseUrl(): string {
|
||||
return this.baseUrl;
|
||||
}
|
||||
|
||||
isRunning(): boolean {
|
||||
return this.process !== null;
|
||||
}
|
||||
}
|
||||
361
hindsight-integrations/moltbot/src/index.ts
Normal file
361
hindsight-integrations/moltbot/src/index.ts
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
import type { MoltbotPluginAPI, PluginConfig } from './types.js';
|
||||
import { HindsightEmbedManager } from './embed-manager.js';
|
||||
import { HindsightClient } from './client.js';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// Module-level state
|
||||
let embedManager: HindsightEmbedManager | null = null;
|
||||
let client: HindsightClient | null = null;
|
||||
|
||||
// Global access for hooks (Moltbot loads hooks separately)
|
||||
if (typeof global !== 'undefined') {
|
||||
(global as any).__hindsightClient = {
|
||||
getClient: () => client,
|
||||
};
|
||||
}
|
||||
|
||||
// Get directory of current module
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Default bank name
|
||||
const BANK_NAME = 'moltbot';
|
||||
|
||||
// Provider mapping: moltbot provider name -> hindsight provider name
|
||||
const PROVIDER_MAP: Record<string, string> = {
|
||||
anthropic: 'anthropic',
|
||||
openai: 'openai',
|
||||
'openai-codex': 'openai',
|
||||
gemini: 'gemini',
|
||||
groq: 'groq',
|
||||
ollama: 'ollama',
|
||||
};
|
||||
|
||||
// Environment variable mapping
|
||||
const ENV_KEY_MAP: Record<string, string> = {
|
||||
anthropic: 'ANTHROPIC_API_KEY',
|
||||
openai: 'OPENAI_API_KEY',
|
||||
'openai-codex': 'OPENAI_API_KEY',
|
||||
gemini: 'GEMINI_API_KEY',
|
||||
groq: 'GROQ_API_KEY',
|
||||
ollama: '', // No key needed for local ollama
|
||||
};
|
||||
|
||||
function detectLLMConfig(api: MoltbotPluginAPI): {
|
||||
provider: string;
|
||||
apiKey: string;
|
||||
model?: string;
|
||||
envKey?: string;
|
||||
} {
|
||||
// Get models from config (agents.defaults.models is a dictionary of models)
|
||||
const models = api.config.agents?.defaults?.models;
|
||||
if (!models || Object.keys(models).length === 0) {
|
||||
throw new Error(
|
||||
'No models configured in Moltbot. Please configure at least one model in agents.defaults.models'
|
||||
);
|
||||
}
|
||||
|
||||
// Try all configured models to find one with an available API key
|
||||
const configuredModels = Object.keys(models);
|
||||
|
||||
for (const modelKey of configuredModels) {
|
||||
const [moltbotProvider, ...modelParts] = modelKey.split('/');
|
||||
const model = modelParts.join('/');
|
||||
const hindsightProvider = PROVIDER_MAP[moltbotProvider];
|
||||
|
||||
if (!hindsightProvider) {
|
||||
continue; // Skip unsupported providers
|
||||
}
|
||||
|
||||
const envKey = ENV_KEY_MAP[moltbotProvider];
|
||||
const apiKey = envKey ? process.env[envKey] || '' : '';
|
||||
|
||||
// For ollama, no key is needed
|
||||
if (hindsightProvider === 'ollama') {
|
||||
return { provider: hindsightProvider, apiKey: '', model, envKey: '' };
|
||||
}
|
||||
|
||||
// If we found a key, use this provider
|
||||
if (apiKey) {
|
||||
return { provider: hindsightProvider, apiKey, model, envKey };
|
||||
}
|
||||
}
|
||||
|
||||
// No API keys found for any provider - show helpful error
|
||||
const configuredProviders = configuredModels
|
||||
.map(m => m.split('/')[0])
|
||||
.filter(p => PROVIDER_MAP[p]);
|
||||
|
||||
const keyInstructions = configuredProviders
|
||||
.map(p => {
|
||||
const envVar = ENV_KEY_MAP[p];
|
||||
return envVar ? ` • ${envVar} (for ${p})` : null;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
|
||||
throw new Error(
|
||||
`No API keys found for Hindsight memory plugin.\n\n` +
|
||||
`Configured providers in Moltbot: ${configuredProviders.join(', ')}\n\n` +
|
||||
`Please set one of these environment variables:\n${keyInstructions}\n\n` +
|
||||
`You can set them in your shell profile (~/.zshrc or ~/.bashrc):\n` +
|
||||
` export ANTHROPIC_API_KEY="your-key-here"\n\n` +
|
||||
`Or run Moltbot with the environment variable:\n` +
|
||||
` ANTHROPIC_API_KEY="your-key" clawdbot start\n\n` +
|
||||
`Alternatively, configure ollama provider which doesn't require an API key.`
|
||||
);
|
||||
}
|
||||
|
||||
function getPluginConfig(api: MoltbotPluginAPI): PluginConfig {
|
||||
const config = api.config.plugins?.entries?.['hindsight-memory']?.config || {};
|
||||
return {
|
||||
bankMission: config.bankMission,
|
||||
embedPort: config.embedPort || 0,
|
||||
daemonIdleTimeout: config.daemonIdleTimeout !== undefined ? config.daemonIdleTimeout : 0,
|
||||
};
|
||||
}
|
||||
|
||||
export default function (api: MoltbotPluginAPI) {
|
||||
try {
|
||||
console.log('[Hindsight] Plugin loading...');
|
||||
|
||||
// Detect LLM configuration from Moltbot
|
||||
console.log('[Hindsight] Detecting LLM config...');
|
||||
const llmConfig = detectLLMConfig(api);
|
||||
|
||||
if (llmConfig.provider === 'ollama') {
|
||||
console.log(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${llmConfig.model || 'default'} (no API key required)`);
|
||||
} else {
|
||||
console.log(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${llmConfig.model || 'default'} (API key: ${llmConfig.envKey})`);
|
||||
}
|
||||
|
||||
console.log('[Hindsight] Getting plugin config...');
|
||||
const pluginConfig = getPluginConfig(api);
|
||||
if (pluginConfig.bankMission) {
|
||||
console.log(`[Hindsight] Custom bank mission configured: "${pluginConfig.bankMission.substring(0, 50)}..."`);
|
||||
}
|
||||
console.log(`[Hindsight] Daemon idle timeout: ${pluginConfig.daemonIdleTimeout}s (0 = never timeout)`);
|
||||
|
||||
// Determine port
|
||||
const port = pluginConfig.embedPort || Math.floor(Math.random() * 10000) + 10000;
|
||||
console.log(`[Hindsight] Port: ${port}`);
|
||||
|
||||
// Register background service
|
||||
console.log('[Hindsight] Registering service...');
|
||||
api.registerService({
|
||||
id: 'hindsight-memory',
|
||||
async start() {
|
||||
try {
|
||||
console.log('[Hindsight] Service starting...');
|
||||
|
||||
// Initialize embed manager
|
||||
console.log('[Hindsight] Creating HindsightEmbedManager...');
|
||||
embedManager = new HindsightEmbedManager(
|
||||
port,
|
||||
llmConfig.provider,
|
||||
llmConfig.apiKey,
|
||||
llmConfig.model,
|
||||
pluginConfig.daemonIdleTimeout
|
||||
);
|
||||
|
||||
// Start the embedded server
|
||||
console.log('[Hindsight] Starting embedded server...');
|
||||
await embedManager.start();
|
||||
|
||||
// Initialize client
|
||||
console.log('[Hindsight] Creating HindsightClient...');
|
||||
client = new HindsightClient(llmConfig.provider, llmConfig.apiKey, llmConfig.model);
|
||||
|
||||
// Use moltbot bank
|
||||
console.log(`[Hindsight] Using bank: ${BANK_NAME}`);
|
||||
client.setBankId(BANK_NAME);
|
||||
|
||||
console.log('[Hindsight] Service ready');
|
||||
} catch (error) {
|
||||
console.error('[Hindsight] Service start error:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async stop() {
|
||||
try {
|
||||
console.log('[Hindsight] Service stopping...');
|
||||
|
||||
if (embedManager) {
|
||||
await embedManager.stop();
|
||||
embedManager = null;
|
||||
}
|
||||
|
||||
client = null;
|
||||
|
||||
console.log('[Hindsight] Service stopped');
|
||||
} catch (error) {
|
||||
console.error('[Hindsight] Service stop error:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
console.log('[Hindsight] Plugin loaded successfully');
|
||||
|
||||
// Register agent_end hook for auto-retention
|
||||
console.log('[Hindsight] Registering agent_end hook...');
|
||||
// Store session key for retention
|
||||
let currentSessionKey: string | undefined;
|
||||
|
||||
// Auto-recall: Inject relevant memories before agent processes the message
|
||||
api.on('before_agent_start', async (context: any) => {
|
||||
try {
|
||||
// Capture session key
|
||||
if (context.sessionKey) {
|
||||
currentSessionKey = context.sessionKey as string;
|
||||
console.log('[Hindsight] Captured session key:', currentSessionKey);
|
||||
}
|
||||
|
||||
// Get the user's latest message for recall
|
||||
let prompt = context.prompt;
|
||||
if (!prompt || typeof prompt !== 'string' || prompt.length < 5) {
|
||||
return; // Skip very short messages
|
||||
}
|
||||
|
||||
// Extract actual message from Telegram format: [Telegram ... GMT+1] actual message
|
||||
const telegramMatch = prompt.match(/\[Telegram[^\]]+\]\s*(.+)$/);
|
||||
if (telegramMatch) {
|
||||
prompt = telegramMatch[1].trim();
|
||||
}
|
||||
|
||||
if (prompt.length < 5) {
|
||||
return; // Skip very short messages after extraction
|
||||
}
|
||||
|
||||
// Get client from global
|
||||
const clientGlobal = (global as any).__hindsightClient;
|
||||
if (!clientGlobal) {
|
||||
return;
|
||||
}
|
||||
|
||||
const client = clientGlobal.getClient();
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Hindsight] Auto-recall for prompt:', prompt.substring(0, 50));
|
||||
|
||||
// Recall relevant memories (up to 1024 tokens)
|
||||
const response = await client.recall({
|
||||
query: prompt,
|
||||
max_tokens: 1024,
|
||||
});
|
||||
|
||||
if (!response.results || response.results.length === 0) {
|
||||
console.log('[Hindsight] No memories found for auto-recall');
|
||||
return;
|
||||
}
|
||||
|
||||
// Format memories for injection
|
||||
const memories = response.results
|
||||
.map((result: any, idx: number) => {
|
||||
const score = result.score ? ` (relevance: ${result.score.toFixed(2)})` : '';
|
||||
return `${idx + 1}. ${result.content}${score}`;
|
||||
})
|
||||
.join('\n\n');
|
||||
|
||||
const contextMessage = `<hindsight-context>
|
||||
You have access to long-term memory from previous conversations. Here are relevant memories:
|
||||
|
||||
${memories}
|
||||
|
||||
Use this context naturally when relevant to the conversation. Don't mention "memory" or "recall" unless specifically asked about past conversations.
|
||||
</hindsight-context>`;
|
||||
|
||||
console.log(`[Hindsight] Auto-recall: Injecting ${response.results.length} memories`);
|
||||
|
||||
// Inject context before the user message
|
||||
return { prependContext: contextMessage };
|
||||
} catch (error) {
|
||||
console.error('[Hindsight] Auto-recall error:', error);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
api.on('agent_end', async (event: any) => {
|
||||
try {
|
||||
console.log('[Hindsight Hook] agent_end triggered');
|
||||
|
||||
// Check event success and messages
|
||||
if (!event.success || !Array.isArray(event.messages) || event.messages.length === 0) {
|
||||
console.log('[Hindsight Hook] Skipping: success:', event.success, 'messages:', event.messages?.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get client from global
|
||||
const clientGlobal = (global as any).__hindsightClient;
|
||||
if (!clientGlobal) {
|
||||
console.warn('[Hindsight] Client global not found, skipping retain');
|
||||
return;
|
||||
}
|
||||
|
||||
const client = clientGlobal.getClient();
|
||||
if (!client) {
|
||||
console.warn('[Hindsight] Client not initialized, skipping retain');
|
||||
return;
|
||||
}
|
||||
|
||||
// Format messages into a transcript
|
||||
const transcript = event.messages
|
||||
.map((msg: any) => {
|
||||
const role = msg.role || 'unknown';
|
||||
let content = '';
|
||||
|
||||
// Handle different content formats
|
||||
if (typeof msg.content === 'string') {
|
||||
content = msg.content;
|
||||
} else if (Array.isArray(msg.content)) {
|
||||
content = msg.content
|
||||
.filter((block: any) => block.type === 'text')
|
||||
.map((block: any) => block.text)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
return `[role: ${role}]\n${content}\n[${role}:end]`;
|
||||
})
|
||||
.join('\n\n');
|
||||
|
||||
if (!transcript.trim() || transcript.length < 10) {
|
||||
console.log('[Hindsight Hook] Transcript too short, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use session key as document ID
|
||||
const documentId = currentSessionKey || 'default-session';
|
||||
|
||||
// Retain to Hindsight
|
||||
await client.retain({
|
||||
content: transcript,
|
||||
document_id: documentId,
|
||||
metadata: {
|
||||
retained_at: new Date().toISOString(),
|
||||
message_count: event.messages.length,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`[Hindsight] Retained ${event.messages.length} messages for session ${documentId}`);
|
||||
} catch (error) {
|
||||
console.error('[Hindsight] Error retaining messages:', error);
|
||||
}
|
||||
});
|
||||
console.log('[Hindsight] Hook registered');
|
||||
} catch (error) {
|
||||
console.error('[Hindsight] Plugin loading error:', error);
|
||||
if (error instanceof Error) {
|
||||
console.error('[Hindsight] Error stack:', error.stack);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Export client getter for tools
|
||||
export function getClient() {
|
||||
return client;
|
||||
}
|
||||
32
hindsight-integrations/moltbot/src/moltbot-types.ts
Normal file
32
hindsight-integrations/moltbot/src/moltbot-types.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Type definitions for moltbot plugin SDK
|
||||
// These are minimal types based on the documentation
|
||||
|
||||
declare module 'moltbot/plugin-sdk' {
|
||||
export interface HookEvent {
|
||||
type: 'command' | 'session' | 'agent' | 'gateway' | 'tool_result_persist';
|
||||
action?: string;
|
||||
sessionKey?: string;
|
||||
timestamp?: string;
|
||||
messages?: string[];
|
||||
context?: {
|
||||
sessionEntry?: {
|
||||
messages?: Array<{
|
||||
role: string;
|
||||
content: string;
|
||||
}>;
|
||||
};
|
||||
sessionId?: string;
|
||||
sessionKey?: string;
|
||||
sessionFile?: string;
|
||||
commandSource?: string;
|
||||
senderId?: string;
|
||||
workspaceDir?: string;
|
||||
bootstrapFiles?: string[];
|
||||
cfg?: any;
|
||||
};
|
||||
}
|
||||
|
||||
export type HookHandler = (event: HookEvent) => Promise<void>;
|
||||
|
||||
export function registerPluginHooksFromDir(api: any, dir: string): void;
|
||||
}
|
||||
84
hindsight-integrations/moltbot/src/types.ts
Normal file
84
hindsight-integrations/moltbot/src/types.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// Moltbot plugin API types (minimal subset needed for this plugin)
|
||||
|
||||
export interface MoltbotPluginAPI {
|
||||
config: MoltbotConfig;
|
||||
registerService(config: ServiceConfig): void;
|
||||
on(event: string, handler: (context: any) => void | Promise<void | { prependContext?: string }>): void;
|
||||
// Add more as needed
|
||||
}
|
||||
|
||||
export interface MoltbotConfig {
|
||||
agents?: {
|
||||
defaults?: {
|
||||
models?: {
|
||||
[modelName: string]: {
|
||||
alias?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
plugins?: {
|
||||
entries?: {
|
||||
[pluginId: string]: {
|
||||
enabled?: boolean;
|
||||
config?: PluginConfig;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginConfig {
|
||||
bankMission?: string;
|
||||
embedPort?: number;
|
||||
daemonIdleTimeout?: number; // Seconds before daemon shuts down (0 = never)
|
||||
}
|
||||
|
||||
export interface ServiceConfig {
|
||||
id: string;
|
||||
start(): Promise<void>;
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
|
||||
// Hindsight API types
|
||||
|
||||
export interface RetainRequest {
|
||||
content: string;
|
||||
document_id?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface RetainResponse {
|
||||
message: string;
|
||||
document_id: string;
|
||||
memory_unit_ids: string[];
|
||||
}
|
||||
|
||||
export interface RecallRequest {
|
||||
query: string;
|
||||
max_tokens?: number;
|
||||
}
|
||||
|
||||
export interface RecallResponse {
|
||||
results: MemoryResult[];
|
||||
}
|
||||
|
||||
export interface MemoryResult {
|
||||
content: string;
|
||||
score: number;
|
||||
metadata?: {
|
||||
document_id?: string;
|
||||
created_at?: string;
|
||||
source?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateBankRequest {
|
||||
name: string;
|
||||
background_context?: string;
|
||||
}
|
||||
|
||||
export interface CreateBankResponse {
|
||||
bank_id: string;
|
||||
name: string;
|
||||
created_at: string;
|
||||
}
|
||||
18
hindsight-integrations/moltbot/tsconfig.json
Normal file
18
hindsight-integrations/moltbot/tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "skills"]
|
||||
}
|
||||
9
hindsight-integrations/moltbot/vitest.config.ts
Normal file
9
hindsight-integrations/moltbot/vitest.config.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
include: ['src/**/*.test.ts'],
|
||||
},
|
||||
});
|
||||
Loading…
Reference in a new issue