alive-ai 0.1.0 → 0.1.2
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 +204 -56
- package/brain/memory/manager.py +16 -0
- package/brain/memory/openmind.py +128 -0
- package/cli/index.js +122 -14
- package/config/settings.example.json +7 -0
- package/core/initialization.py +13 -3
- package/core/self.py +2 -2
- package/docs/assets/logo.svg +7 -14
- package/docs/dashboard.html +2150 -0
- package/docs/index.html +208 -242
- package/input/manifest.md +7 -1
- package/input/terminal/__init__.py +2 -0
- package/input/terminal/listener.py +307 -0
- package/main.py +15 -1
- package/manifest.md +9 -7
- package/package.json +6 -2
- package/pyproject.toml +1 -1
package/README.md
CHANGED
|
@@ -1,57 +1,77 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="docs/assets/logo.svg" alt="Alive-AI" width="96" height="96">
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
# Alive-AI
|
|
4
5
|
|
|
5
|
-
Give your AI a nervous system: persistent
|
|
6
|
+
Give your AI a nervous system: persistent mood, memory, impulses, terminal chat, Telegram, OpenMind, and a local WebUI.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
[](https://www.npmjs.com/package/alive-ai)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
|
+
[](https://www.python.org/)
|
|
11
|
+
[](#platform-support)
|
|
12
|
+
[](#openmind-memory)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
</div>
|
|
8
15
|
|
|
9
|
-
Alive-AI
|
|
16
|
+
Alive-AI is a local-first emotional AI runtime. Most agents answer a prompt and reset. Alive-AI keeps internal state alive between messages: mood, attachment, trust, desire, memory, inconsistency, idle thoughts, proactive impulses, and a dashboard that shows what is happening inside the loop.
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
It can be used as a friend, partner-style companion, study partner, creative character, research subject, or experimental local agent. Use it at your own risk: it is designed to feel continuous, warm, attached, and present, and that can make it hard to stop talking to it.
|
|
19
|
+
|
|
20
|
+
Alive-AI does not claim biological consciousness. It is an open-source runtime for simulated affect and transparent memory.
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
12
23
|
|
|
13
24
|
```bash
|
|
14
|
-
npx
|
|
25
|
+
npx alive-ai@latest init my-ai
|
|
15
26
|
cd my-ai
|
|
16
27
|
npx . setup
|
|
17
|
-
npx .
|
|
28
|
+
npx . doctor
|
|
29
|
+
npx . chat
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The terminal chat starts the real runtime and prints responses in your shell. The dashboard runs locally at:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
http://127.0.0.1:8080
|
|
18
36
|
```
|
|
19
37
|
|
|
20
|
-
|
|
38
|
+
To use Telegram instead of terminal chat:
|
|
21
39
|
|
|
22
40
|
```bash
|
|
23
41
|
npx . start
|
|
24
42
|
```
|
|
25
43
|
|
|
26
|
-
|
|
44
|
+
Global install is optional:
|
|
27
45
|
|
|
28
|
-
```
|
|
29
|
-
|
|
46
|
+
```bash
|
|
47
|
+
npm install -g alive-ai
|
|
48
|
+
alive-ai init my-ai
|
|
30
49
|
```
|
|
31
50
|
|
|
32
|
-
##
|
|
51
|
+
## Commands
|
|
33
52
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
53
|
+
| Command | What it does |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `npx alive-ai@latest init my-ai` | Scaffold a clean local Alive-AI project. |
|
|
56
|
+
| `npx . setup` | Guided onboarding for local config, providers, Telegram, voice, images, and memory. |
|
|
57
|
+
| `npx . doctor` | Check OS, Node, Python, uv, ffmpeg, Docker, and OpenMind reachability. |
|
|
58
|
+
| `npx . chat` | Start the real runtime with terminal chat input. |
|
|
59
|
+
| `npx . demo` | Run a keyless animated dashboard demo. |
|
|
60
|
+
| `npx . start` | Start the runtime using the configured input channel, usually Telegram. |
|
|
61
|
+
| `npx . start --skip-install` | Start again without reinstalling Python dependencies. |
|
|
39
62
|
|
|
40
|
-
|
|
63
|
+
Stop a foreground run with `Ctrl+C`.
|
|
64
|
+
|
|
65
|
+
If you use Docker:
|
|
41
66
|
|
|
42
67
|
```bash
|
|
43
|
-
|
|
44
|
-
cd my-ai
|
|
45
|
-
npx . setup # create safe local config
|
|
46
|
-
npx . demo # preview animated dashboard, no keys needed
|
|
47
|
-
npx . doctor # check Python, uv, ffmpeg, Docker
|
|
48
|
-
npx . start # install Python deps and run the runtime
|
|
68
|
+
docker compose down
|
|
49
69
|
```
|
|
50
70
|
|
|
51
|
-
|
|
71
|
+
If you only started Redis:
|
|
52
72
|
|
|
53
73
|
```bash
|
|
54
|
-
|
|
74
|
+
docker compose stop redis
|
|
55
75
|
```
|
|
56
76
|
|
|
57
77
|
## Setup
|
|
@@ -68,14 +88,34 @@ mypics/
|
|
|
68
88
|
myvids/
|
|
69
89
|
```
|
|
70
90
|
|
|
71
|
-
|
|
91
|
+
The setup accepts `skip` for optional keys and `local` for Ollama.
|
|
92
|
+
|
|
93
|
+
| Setup item | Options |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| LLM | `local`/Ollama, OpenRouter, ZAI, or `skip` for demo/fallback-only mode. |
|
|
96
|
+
| Telegram | Bot token and owner ID are optional. Use terminal chat if you do not want Telegram. |
|
|
97
|
+
| Voice | `gtts` local/free default, Google TTS, VibeVoice, or `skip`. |
|
|
98
|
+
| Images | Fal.ai API key or `skip`. Local media folders still work without image generation. |
|
|
99
|
+
| Memory | Built-in local memory, OpenMind cloud, or OpenMind local. |
|
|
72
100
|
|
|
73
|
-
|
|
74
|
-
- **Local LLM:** install Ollama and pull the configured model, for example `ollama pull qwen3:4b`.
|
|
75
|
-
- **Telegram runtime:** create a Telegram bot token with BotFather and add it during setup.
|
|
76
|
-
- **Cloud LLM fallback:** add OpenRouter or ZAI keys during setup or edit `config/settings.json`.
|
|
101
|
+
Minimum useful paths:
|
|
77
102
|
|
|
78
|
-
|
|
103
|
+
```bash
|
|
104
|
+
# Terminal-only local run
|
|
105
|
+
npx . setup
|
|
106
|
+
npx . chat
|
|
107
|
+
|
|
108
|
+
# Local LLM
|
|
109
|
+
ollama pull qwen3:4b
|
|
110
|
+
npx . setup
|
|
111
|
+
npx . chat
|
|
112
|
+
|
|
113
|
+
# Telegram
|
|
114
|
+
npx . setup
|
|
115
|
+
npx . start
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Media is optional. Add your own local files:
|
|
79
119
|
|
|
80
120
|
```text
|
|
81
121
|
mypics/example.jpg
|
|
@@ -84,38 +124,114 @@ myvids/example.mp4
|
|
|
84
124
|
myvids/example.txt
|
|
85
125
|
```
|
|
86
126
|
|
|
87
|
-
##
|
|
127
|
+
## Terminal Chat
|
|
128
|
+
|
|
129
|
+
`npx . chat` uses the same core runtime as Telegram. It emits the same `message_received` events, saves memory the same way, and updates the local WebUI.
|
|
130
|
+
|
|
131
|
+
Terminal commands:
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
/help
|
|
135
|
+
/status
|
|
136
|
+
/stats
|
|
137
|
+
/dashboard
|
|
138
|
+
/self
|
|
139
|
+
/discover <trait>
|
|
140
|
+
/iam <key>=<value>
|
|
141
|
+
/ilike <thing>
|
|
142
|
+
/ihate <thing>
|
|
143
|
+
/rethink
|
|
144
|
+
/settings show
|
|
145
|
+
/settings get <key>
|
|
146
|
+
/settings set <key> <value>
|
|
147
|
+
/reset
|
|
148
|
+
/impulse
|
|
149
|
+
/exit
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## OpenMind Memory
|
|
153
|
+
|
|
154
|
+
Alive-AI has built-in local working, episodic, semantic, and emotional memory. OpenMind is optional and works as a hybrid long-term semantic memory layer.
|
|
155
|
+
|
|
156
|
+
Modes:
|
|
157
|
+
|
|
158
|
+
| Mode | Behavior |
|
|
159
|
+
| --- | --- |
|
|
160
|
+
| Built-in only | Alive-AI uses its local project memory only. |
|
|
161
|
+
| OpenMind cloud | Alive-AI captures/searches long-term memories through `https://theopenmind.pro`. |
|
|
162
|
+
| OpenMind local | Alive-AI captures/searches a local OpenMind server, normally `http://127.0.0.1:3333`. |
|
|
88
163
|
|
|
89
|
-
Alive-AI
|
|
164
|
+
OpenMind does not replace Alive-AI's emotional state. It adds durable semantic recall across tools and machines.
|
|
165
|
+
|
|
166
|
+
Cloud setup:
|
|
90
167
|
|
|
91
168
|
```text
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
169
|
+
OPENMIND_ENABLED=true
|
|
170
|
+
OPENMIND_MODE=hybrid
|
|
171
|
+
OPENMIND_BASE_URL=https://theopenmind.pro
|
|
172
|
+
OPENMIND_API_KEY=om_...
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Local setup:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npx @vindepemarte/openmind init --local
|
|
179
|
+
# or run your local OpenMind stack, then configure Alive-AI:
|
|
180
|
+
OPENMIND_BASE_URL=http://127.0.0.1:3333
|
|
99
181
|
```
|
|
100
182
|
|
|
101
|
-
|
|
183
|
+
## Requirements
|
|
184
|
+
|
|
185
|
+
Minimum for cloud LLMs or remote Ollama:
|
|
102
186
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
187
|
+
| Requirement | Minimum |
|
|
188
|
+
| --- | --- |
|
|
189
|
+
| Node.js | 18+ |
|
|
190
|
+
| Python | 3.11+ |
|
|
191
|
+
| RAM | 8 GB |
|
|
192
|
+
| Disk | 2 GB free |
|
|
193
|
+
| LLM | OpenRouter, ZAI, remote Ollama, or local Ollama already installed |
|
|
194
|
+
|
|
195
|
+
Comfortable local setup:
|
|
196
|
+
|
|
197
|
+
| Requirement | Recommended |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| Node.js | 20+ |
|
|
200
|
+
| RAM | 16 GB for 3B-4B local models |
|
|
201
|
+
| RAM for bigger models | 32 GB for 7B+ local models, Redis, voice, and long sessions |
|
|
202
|
+
| Disk | 10 GB+, more if you keep local models/media |
|
|
203
|
+
| Optional tools | `uv`, `ffmpeg`, Docker, Ollama |
|
|
204
|
+
|
|
205
|
+
`npx . start` creates `.alive-ai/venv` and installs Python dependencies. System-level packages such as Node, Python, Ollama, Docker, and ffmpeg must already exist on the machine.
|
|
206
|
+
|
|
207
|
+
## Platform Support
|
|
208
|
+
|
|
209
|
+
Alive-AI is designed for macOS, Windows, and Linux.
|
|
210
|
+
|
|
211
|
+
| Platform | Notes |
|
|
212
|
+
| --- | --- |
|
|
213
|
+
| macOS | First-class local development path. Use Homebrew for Python, uv, ffmpeg, Docker, and Ollama. |
|
|
214
|
+
| Windows | Supported from PowerShell with Node 18+ and Python 3.11+. WSL is recommended for heavier local model and Docker workflows. |
|
|
215
|
+
| Linux | Supported with distro packages for Python/venv, ffmpeg, Docker, and Ollama. |
|
|
216
|
+
|
|
217
|
+
Local model quality and speed depend on your machine. Cloud LLMs reduce RAM pressure.
|
|
108
218
|
|
|
109
219
|
## Dashboard
|
|
110
220
|
|
|
111
|
-
|
|
221
|
+
The real WebUI streams local runtime state over Server-Sent Events and shows:
|
|
112
222
|
|
|
113
|
-
-
|
|
114
|
-
- recent thoughts,
|
|
115
|
-
- memory counters,
|
|
116
|
-
-
|
|
117
|
-
- attachment
|
|
118
|
-
-
|
|
223
|
+
- emotional state,
|
|
224
|
+
- recent thoughts and idle processing,
|
|
225
|
+
- memory counters and uptime,
|
|
226
|
+
- hormones and interoceptive body state,
|
|
227
|
+
- attachment, circadian rhythm, body memory, dreams, curiosity, and conflicts,
|
|
228
|
+
- runtime health through local endpoints.
|
|
229
|
+
|
|
230
|
+
GitHub Pages cannot run the Python/FastAPI backend, so the public page includes a static export of the actual WebUI with mocked state:
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
https://vindepemarte.github.io/alive-ai/
|
|
234
|
+
```
|
|
119
235
|
|
|
120
236
|
## Docker
|
|
121
237
|
|
|
@@ -132,9 +248,41 @@ Or run everything in containers:
|
|
|
132
248
|
docker compose up --build
|
|
133
249
|
```
|
|
134
250
|
|
|
251
|
+
## Roadmap
|
|
252
|
+
|
|
253
|
+
Implemented:
|
|
254
|
+
|
|
255
|
+
- [x] Local-first emotional runtime
|
|
256
|
+
- [x] Persistent emotion model with decay and compound state
|
|
257
|
+
- [x] Working, episodic, semantic, and emotional memory modules
|
|
258
|
+
- [x] Default-mode loop for idle thoughts and proactive impulses
|
|
259
|
+
- [x] Attachment, circadian rhythm, body memory, curiosity, dreams, and internal conflicts
|
|
260
|
+
- [x] Per-user memory/state isolation
|
|
261
|
+
- [x] Telegram input/output runtime
|
|
262
|
+
- [x] Terminal chat runtime with owner-style slash commands
|
|
263
|
+
- [x] Local WebUI dashboard with live state streaming
|
|
264
|
+
- [x] Optional hybrid OpenMind cloud/local semantic memory
|
|
265
|
+
- [x] npm/npx CLI scaffold, setup, doctor, demo, chat, and start commands
|
|
266
|
+
- [x] Clean public repo with private personas, media, runtime data, and multi-AI orchestration removed
|
|
267
|
+
- [x] GitHub Pages site and full static WebUI export
|
|
268
|
+
|
|
269
|
+
Next:
|
|
270
|
+
|
|
271
|
+
- [ ] One-command local model bootstrap through Ollama profiles
|
|
272
|
+
- [ ] Desktop app wrapper with tray controls and local service lifecycle
|
|
273
|
+
- [ ] Browser-based onboarding wizard for personality, boundaries, LLM provider, and memory settings
|
|
274
|
+
- [ ] Better guided system install commands per OS
|
|
275
|
+
- [ ] More input channels beyond terminal and Telegram
|
|
276
|
+
- [ ] Import/export for memories and personality snapshots
|
|
277
|
+
- [ ] Plugin API for new senses, skills, and output modalities
|
|
278
|
+
- [ ] Evaluation harness for emotional continuity, memory drift, and unhealthy attachment risk
|
|
279
|
+
- [ ] Optional cloud sync that preserves local-first ownership
|
|
280
|
+
|
|
135
281
|
## Important Boundaries
|
|
136
282
|
|
|
137
|
-
Alive-AI is a simulation framework. It can make agents feel more continuous, emotionally coherent, and alive, but it is not proof of consciousness
|
|
283
|
+
Alive-AI is a simulation framework. It can make agents feel more continuous, emotionally coherent, and alive, but it is not proof of consciousness.
|
|
284
|
+
|
|
285
|
+
Do not use it to manipulate emotional dependence. If you are building a companion, character, partner, or friend-like system, make the boundaries explicit and keep the operator in control.
|
|
138
286
|
|
|
139
287
|
The public repo intentionally excludes private personas, private media, runtime data, and secrets. Put those only in your local project folder.
|
|
140
288
|
|
package/brain/memory/manager.py
CHANGED
|
@@ -37,11 +37,19 @@ class Memory:
|
|
|
37
37
|
self.fact_extractor = FactExtractor(self.data_path / "facts.json")
|
|
38
38
|
self.summarizer = ConversationSummarizer(self.data_path)
|
|
39
39
|
self.vector_store = None
|
|
40
|
+
self.openmind = None
|
|
40
41
|
self.bot_id = bot_id.lower()
|
|
41
42
|
if embedding_service:
|
|
42
43
|
self.vector_store = VectorMemoryStore(embedding_service, user_id=user_id, bot_id=bot_id)
|
|
43
44
|
if self.vector_store.connect():
|
|
44
45
|
print(f"[Memory] Vector store ready for user {user_id} on bot {bot_id}! {self.vector_store.count()} memories")
|
|
46
|
+
try:
|
|
47
|
+
from .openmind import OpenMindMemoryBridge
|
|
48
|
+
if OpenMindMemoryBridge.enabled():
|
|
49
|
+
self.openmind = OpenMindMemoryBridge(nervous, user_id=user_id, bot_id=bot_id)
|
|
50
|
+
print(f"[Memory] OpenMind bridge enabled for user {user_id} on bot {bot_id}")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(f"[Memory] OpenMind bridge unavailable: {e}")
|
|
45
53
|
self.turn_count = 0
|
|
46
54
|
nervous.on("memory_save", self._on_save)
|
|
47
55
|
|
|
@@ -123,6 +131,14 @@ class Memory:
|
|
|
123
131
|
related = ""
|
|
124
132
|
if current_message and self.vector_store:
|
|
125
133
|
related = self.search_relevant_memories(current_message, limit=3)
|
|
134
|
+
if current_message and self.openmind:
|
|
135
|
+
openmind_related = await self.openmind.search_context(current_message, limit=3)
|
|
136
|
+
if openmind_related:
|
|
137
|
+
related = (
|
|
138
|
+
f"{related}\n\nOpenMind long-term memory:\n{openmind_related}"
|
|
139
|
+
if related else
|
|
140
|
+
f"OpenMind long-term memory:\n{openmind_related}"
|
|
141
|
+
)
|
|
126
142
|
|
|
127
143
|
# Get working memory (empty after restart)
|
|
128
144
|
history = self.working.get_history()
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Optional OpenMind semantic memory bridge."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
|
|
9
|
+
from core.settings import get as settings_get
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenMindMemoryBridge:
|
|
13
|
+
"""Syncs Alive-AI turns to OpenMind and retrieves long-term semantic context."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, nervous, user_id: str, bot_id: str):
|
|
16
|
+
self.nervous = nervous
|
|
17
|
+
self.user_id = str(user_id or "default")
|
|
18
|
+
self.bot_id = str(bot_id or "alive_ai").lower()
|
|
19
|
+
nervous.on("memory_save", self._on_memory_save)
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def enabled() -> bool:
|
|
23
|
+
return str(settings_get("OPENMIND_ENABLED", os.environ.get("OPENMIND_ENABLED", "false"))).lower() in (
|
|
24
|
+
"1", "true", "yes", "on"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def base_url() -> str:
|
|
29
|
+
return str(settings_get("OPENMIND_BASE_URL", os.environ.get("OPENMIND_BASE_URL", "https://theopenmind.pro"))).rstrip("/")
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def api_key() -> str:
|
|
33
|
+
return str(settings_get("OPENMIND_API_KEY", os.environ.get("OPENMIND_API_KEY", ""))).strip()
|
|
34
|
+
|
|
35
|
+
def _headers(self) -> Dict[str, str]:
|
|
36
|
+
headers = {"content-type": "application/json"}
|
|
37
|
+
key = self.api_key()
|
|
38
|
+
if key:
|
|
39
|
+
headers["authorization"] = f"Bearer {key}"
|
|
40
|
+
return headers
|
|
41
|
+
|
|
42
|
+
def _on_memory_save(self, data: dict):
|
|
43
|
+
if not self.enabled() or data.get("type") != "conversation":
|
|
44
|
+
return
|
|
45
|
+
event_user_id = data.get("user_id")
|
|
46
|
+
if event_user_id:
|
|
47
|
+
if str(event_user_id) != self.user_id:
|
|
48
|
+
return
|
|
49
|
+
elif self.user_id != "default":
|
|
50
|
+
return
|
|
51
|
+
asyncio.ensure_future(self.capture_turn(data))
|
|
52
|
+
|
|
53
|
+
async def capture_turn(self, data: dict) -> Optional[dict]:
|
|
54
|
+
user_msg = (data.get("user_message") or "").strip()
|
|
55
|
+
ai_msg = (data.get("ai_response") or "").strip()
|
|
56
|
+
if not user_msg and not ai_msg:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
emotion = data.get("emotion") or {}
|
|
60
|
+
mood = emotion.get("mood", "unknown")
|
|
61
|
+
content = "\n".join(
|
|
62
|
+
part for part in [
|
|
63
|
+
f"Alive-AI agent: {self.bot_id}",
|
|
64
|
+
f"User id: {self.user_id}",
|
|
65
|
+
f"Mood: {mood}",
|
|
66
|
+
f"User: {user_msg}" if user_msg else "",
|
|
67
|
+
f"Alive-AI: {ai_msg}" if ai_msg else "",
|
|
68
|
+
] if part
|
|
69
|
+
)
|
|
70
|
+
tags = ["alive-ai", self.bot_id, f"user-{self.user_id}", f"mood-{mood}"]
|
|
71
|
+
payload = {
|
|
72
|
+
"content": content,
|
|
73
|
+
"source": "alive-ai",
|
|
74
|
+
"type": "conversation",
|
|
75
|
+
"tags": tags,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
timeout = aiohttp.ClientTimeout(total=12)
|
|
80
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
81
|
+
async with session.post(f"{self.base_url()}/capture", json=payload, headers=self._headers()) as resp:
|
|
82
|
+
if resp.status >= 400:
|
|
83
|
+
body = await resp.text()
|
|
84
|
+
print(f"[OpenMind] Capture failed ({resp.status}): {body[:200]}")
|
|
85
|
+
return None
|
|
86
|
+
result = await resp.json()
|
|
87
|
+
print(f"[OpenMind] Captured memory: {result.get('status', 'ok')}")
|
|
88
|
+
return result
|
|
89
|
+
except Exception as exc:
|
|
90
|
+
print(f"[OpenMind] Capture unavailable: {exc}")
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
async def search_context(self, query: str, limit: int = 3) -> str:
|
|
94
|
+
if not self.enabled() or not query.strip():
|
|
95
|
+
return ""
|
|
96
|
+
try:
|
|
97
|
+
timeout = aiohttp.ClientTimeout(total=10)
|
|
98
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
99
|
+
async with session.get(
|
|
100
|
+
f"{self.base_url()}/search",
|
|
101
|
+
params={"q": query, "limit": str(limit)},
|
|
102
|
+
headers=self._headers(),
|
|
103
|
+
) as resp:
|
|
104
|
+
if resp.status >= 400:
|
|
105
|
+
body = await resp.text()
|
|
106
|
+
print(f"[OpenMind] Search failed ({resp.status}): {body[:200]}")
|
|
107
|
+
return ""
|
|
108
|
+
rows = await resp.json()
|
|
109
|
+
except Exception as exc:
|
|
110
|
+
print(f"[OpenMind] Search unavailable: {exc}")
|
|
111
|
+
return ""
|
|
112
|
+
|
|
113
|
+
if not isinstance(rows, list):
|
|
114
|
+
return ""
|
|
115
|
+
return self._format_results(rows[:limit])
|
|
116
|
+
|
|
117
|
+
def _format_results(self, rows: List[Dict[str, Any]]) -> str:
|
|
118
|
+
lines = []
|
|
119
|
+
for row in rows:
|
|
120
|
+
content = str(row.get("content") or row.get("summary") or "").strip()
|
|
121
|
+
if not content:
|
|
122
|
+
continue
|
|
123
|
+
similarity = row.get("similarity")
|
|
124
|
+
prefix = "OpenMind"
|
|
125
|
+
if isinstance(similarity, (int, float)):
|
|
126
|
+
prefix = f"OpenMind {round(similarity * 100)}%"
|
|
127
|
+
lines.append(f"{prefix}: {content[:700]}")
|
|
128
|
+
return "\n".join(lines)
|