alive-ai 0.1.2 → 0.1.3
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 +27 -4
- package/cli/index.js +284 -15
- package/cli/tui.js +248 -0
- package/config/settings.example.json +1 -0
- package/core/hot_reload.py +2 -1
- package/core/manifest.md +2 -0
- package/core/paths.py +48 -0
- package/core/self.py +42 -1
- package/core/user_manager.py +2 -4
- package/docs/index.html +4 -3
- package/heart/emotional_state.py +2 -2
- package/heart/hormonal.py +2 -2
- package/heart/integrity.py +2 -1
- package/heart/interoception.py +2 -5
- package/heart/love.py +2 -2
- package/heart/scars.py +2 -3
- package/heart/telemetry.py +3 -9
- package/input/manifest.md +2 -2
- package/input/telegram/listener.py +47 -10
- package/input/terminal/listener.py +51 -46
- package/main.py +50 -15
- package/manifest.md +1 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/webui/app.py +15 -18
- package/webui/bridge.py +22 -1
package/cli/tui.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const readline = require("readline");
|
|
4
|
+
|
|
5
|
+
const EVENT_PREFIX = "__ALIVE_AI_TUI__";
|
|
6
|
+
|
|
7
|
+
function stripAnsi(value) {
|
|
8
|
+
return String(value || "").replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function truncate(value, width) {
|
|
12
|
+
const text = stripAnsi(value);
|
|
13
|
+
if (width <= 1) return "";
|
|
14
|
+
return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}…` : text;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function wrap(value, width) {
|
|
18
|
+
const words = stripAnsi(value).replace(/\r/g, "").split(/\s+/);
|
|
19
|
+
const lines = [];
|
|
20
|
+
let current = "";
|
|
21
|
+
for (const word of words) {
|
|
22
|
+
if (!word) continue;
|
|
23
|
+
if (!current) {
|
|
24
|
+
current = word;
|
|
25
|
+
} else if (`${current} ${word}`.length <= width) {
|
|
26
|
+
current += ` ${word}`;
|
|
27
|
+
} else {
|
|
28
|
+
lines.push(current);
|
|
29
|
+
current = word;
|
|
30
|
+
}
|
|
31
|
+
while (current.length > width) {
|
|
32
|
+
lines.push(current.slice(0, width));
|
|
33
|
+
current = current.slice(width);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (current) lines.push(current);
|
|
37
|
+
return lines.length ? lines : [""];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function boxLine(left, right, leftWidth, rightWidth) {
|
|
41
|
+
return `│ ${truncate(left, leftWidth).padEnd(leftWidth)} │ ${truncate(right, rightWidth).padEnd(rightWidth)} │`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function formatChat(entry, width) {
|
|
45
|
+
const label = entry.role === "user" ? "You" : entry.role === "proactive" ? "Alice proactive" : entry.role === "assistant" ? "Alice" : "System";
|
|
46
|
+
const prefix = `${label}: `;
|
|
47
|
+
const lines = wrap(entry.text, Math.max(10, width - prefix.length));
|
|
48
|
+
return lines.map((line, index) => `${index === 0 ? prefix : " ".repeat(prefix.length)}${line}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function runRuntimeTui(child, options = {}) {
|
|
52
|
+
const state = {
|
|
53
|
+
chat: [],
|
|
54
|
+
logs: [],
|
|
55
|
+
input: "",
|
|
56
|
+
status: "starting",
|
|
57
|
+
dashboard: options.dashboard || "http://127.0.0.1:8080",
|
|
58
|
+
stopping: false,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
let stdoutBuffer = "";
|
|
62
|
+
let stderrBuffer = "";
|
|
63
|
+
let renderTimer = null;
|
|
64
|
+
const isTty = process.stdin.isTTY && process.stdout.isTTY;
|
|
65
|
+
|
|
66
|
+
function addChat(role, text) {
|
|
67
|
+
state.chat.push({ role, text: String(text || "") });
|
|
68
|
+
if (state.chat.length > 200) state.chat = state.chat.slice(-200);
|
|
69
|
+
scheduleRender();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function addLog(text) {
|
|
73
|
+
for (const rawLine of String(text || "").split(/\n/)) {
|
|
74
|
+
const line = stripAnsi(rawLine).trimEnd();
|
|
75
|
+
if (!line || line === ">") continue;
|
|
76
|
+
state.logs.push(line);
|
|
77
|
+
}
|
|
78
|
+
if (state.logs.length > 400) state.logs = state.logs.slice(-400);
|
|
79
|
+
scheduleRender();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function handleLine(line, stream) {
|
|
83
|
+
const clean = line.trimEnd();
|
|
84
|
+
if (!clean) return;
|
|
85
|
+
if (clean.startsWith(EVENT_PREFIX)) {
|
|
86
|
+
try {
|
|
87
|
+
const event = JSON.parse(clean.slice(EVENT_PREFIX.length));
|
|
88
|
+
addChat(event.role || "system", event.text || "");
|
|
89
|
+
return;
|
|
90
|
+
} catch {
|
|
91
|
+
addLog(clean);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
addLog(stream === "stderr" ? `[stderr] ${clean}` : clean);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function consume(chunk, stream) {
|
|
99
|
+
let buffer = stream === "stderr" ? stderrBuffer : stdoutBuffer;
|
|
100
|
+
buffer += chunk.toString();
|
|
101
|
+
const lines = buffer.split(/\n/);
|
|
102
|
+
buffer = lines.pop() || "";
|
|
103
|
+
for (const line of lines) handleLine(line, stream);
|
|
104
|
+
if (stream === "stderr") stderrBuffer = buffer;
|
|
105
|
+
else stdoutBuffer = buffer;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function scheduleRender() {
|
|
109
|
+
if (!isTty) return;
|
|
110
|
+
if (renderTimer) return;
|
|
111
|
+
renderTimer = setTimeout(() => {
|
|
112
|
+
renderTimer = null;
|
|
113
|
+
render();
|
|
114
|
+
}, 30);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function render() {
|
|
118
|
+
if (!isTty) return;
|
|
119
|
+
const width = Math.max(80, process.stdout.columns || 100);
|
|
120
|
+
const height = Math.max(24, process.stdout.rows || 30);
|
|
121
|
+
const leftWidth = Math.max(34, Math.floor((width - 7) * 0.58));
|
|
122
|
+
const rightWidth = Math.max(28, width - leftWidth - 7);
|
|
123
|
+
const bodyHeight = height - 7;
|
|
124
|
+
|
|
125
|
+
const chatLines = [];
|
|
126
|
+
for (const entry of state.chat) {
|
|
127
|
+
chatLines.push(...formatChat(entry, leftWidth), "");
|
|
128
|
+
}
|
|
129
|
+
const visibleChat = chatLines.slice(-bodyHeight);
|
|
130
|
+
const visibleLogs = state.logs.slice(-bodyHeight);
|
|
131
|
+
|
|
132
|
+
const top = `┌${"─".repeat(leftWidth + 2)}┬${"─".repeat(rightWidth + 2)}┐`;
|
|
133
|
+
const sep = `├${"─".repeat(leftWidth + 2)}┼${"─".repeat(rightWidth + 2)}┤`;
|
|
134
|
+
const bottom = `└${"─".repeat(leftWidth + 2)}┴${"─".repeat(rightWidth + 2)}┘`;
|
|
135
|
+
const inputLine = `> ${state.input}`;
|
|
136
|
+
|
|
137
|
+
const lines = [
|
|
138
|
+
top,
|
|
139
|
+
boxLine("Chat", `Logs • ${state.status} • ${state.dashboard}`, leftWidth, rightWidth),
|
|
140
|
+
sep,
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
for (let index = 0; index < bodyHeight; index += 1) {
|
|
144
|
+
lines.push(boxLine(visibleChat[index] || "", visibleLogs[index] || "", leftWidth, rightWidth));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
lines.push(bottom);
|
|
148
|
+
lines.push(truncate(inputLine, width - 1));
|
|
149
|
+
lines.push(truncate("Enter sends • /help commands • /exit stops • Ctrl+C stops", width - 1));
|
|
150
|
+
|
|
151
|
+
process.stdout.write("\x1b[H\x1b[2J");
|
|
152
|
+
process.stdout.write(lines.join("\n"));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function restoreTerminal() {
|
|
156
|
+
if (!isTty) return;
|
|
157
|
+
process.stdin.setRawMode(false);
|
|
158
|
+
process.stdout.write("\x1b[?25h\x1b[?1049l");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function stopChild() {
|
|
162
|
+
if (state.stopping) return;
|
|
163
|
+
state.stopping = true;
|
|
164
|
+
state.status = "stopping";
|
|
165
|
+
scheduleRender();
|
|
166
|
+
try {
|
|
167
|
+
if (child.stdin.writable) child.stdin.write("/exit\n");
|
|
168
|
+
} catch {}
|
|
169
|
+
setTimeout(() => {
|
|
170
|
+
if (!child.killed) child.kill("SIGINT");
|
|
171
|
+
}, 1200).unref();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function sendLine() {
|
|
175
|
+
const line = state.input.trim();
|
|
176
|
+
state.input = "";
|
|
177
|
+
if (!line) {
|
|
178
|
+
scheduleRender();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
addChat("user", line);
|
|
182
|
+
if (line === "/exit" || line === "/quit" || line === "/stop") {
|
|
183
|
+
state.status = "stopping";
|
|
184
|
+
state.stopping = true;
|
|
185
|
+
}
|
|
186
|
+
child.stdin.write(`${line}\n`);
|
|
187
|
+
scheduleRender();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return new Promise((resolve) => {
|
|
191
|
+
if (!isTty) {
|
|
192
|
+
child.stdout.on("data", (chunk) => process.stdout.write(chunk));
|
|
193
|
+
child.stderr.on("data", (chunk) => process.stderr.write(chunk));
|
|
194
|
+
child.on("exit", (code) => resolve(code || 0));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
process.stdout.write("\x1b[?1049h\x1b[?25l");
|
|
199
|
+
readline.emitKeypressEvents(process.stdin);
|
|
200
|
+
process.stdin.setRawMode(true);
|
|
201
|
+
process.stdin.resume();
|
|
202
|
+
|
|
203
|
+
addChat("system", "Type a message and press Enter. The runtime logs stay on the right.");
|
|
204
|
+
child.stdout.on("data", (chunk) => consume(chunk, "stdout"));
|
|
205
|
+
child.stderr.on("data", (chunk) => consume(chunk, "stderr"));
|
|
206
|
+
|
|
207
|
+
const onKeypress = (str, key = {}) => {
|
|
208
|
+
if (key.ctrl && key.name === "c") {
|
|
209
|
+
stopChild();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (key.name === "return") {
|
|
213
|
+
sendLine();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (key.name === "backspace") {
|
|
217
|
+
state.input = state.input.slice(0, -1);
|
|
218
|
+
scheduleRender();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (key.name === "escape") {
|
|
222
|
+
state.input = "";
|
|
223
|
+
scheduleRender();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (str && !key.ctrl && !key.meta) {
|
|
227
|
+
state.input += str;
|
|
228
|
+
scheduleRender();
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
process.stdin.on("keypress", onKeypress);
|
|
233
|
+
process.stdout.on("resize", scheduleRender);
|
|
234
|
+
render();
|
|
235
|
+
|
|
236
|
+
child.on("exit", (code) => {
|
|
237
|
+
if (stdoutBuffer) handleLine(stdoutBuffer, "stdout");
|
|
238
|
+
if (stderrBuffer) handleLine(stderrBuffer, "stderr");
|
|
239
|
+
process.stdin.off("keypress", onKeypress);
|
|
240
|
+
process.stdout.off("resize", scheduleRender);
|
|
241
|
+
restoreTerminal();
|
|
242
|
+
console.log(`Alive-AI stopped${code ? ` with exit code ${code}` : ""}.`);
|
|
243
|
+
resolve(code || 0);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
module.exports = { runRuntimeTui };
|
package/core/hot_reload.py
CHANGED
|
@@ -10,6 +10,7 @@ import threading
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from watchdog.observers import Observer
|
|
12
12
|
from watchdog.events import FileSystemEventHandler, FileModifiedEvent
|
|
13
|
+
from .paths import project_root
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class HotReloader:
|
|
@@ -24,7 +25,7 @@ class HotReloader:
|
|
|
24
25
|
self.pending_reload = False
|
|
25
26
|
self.observer = None
|
|
26
27
|
self.watched_dirs = ["core", "brain", "heart", "config", "input", "output"]
|
|
27
|
-
self.base_path =
|
|
28
|
+
self.base_path = project_root()
|
|
28
29
|
|
|
29
30
|
def start(self):
|
|
30
31
|
"""Start watching for file changes"""
|
package/core/manifest.md
CHANGED
|
@@ -7,10 +7,12 @@ The foundation of the AI. Always required.
|
|
|
7
7
|
- `config.py` - Configuration loader
|
|
8
8
|
- `state.py` - Global state management
|
|
9
9
|
- `self.py` - The Self (main coordinator)
|
|
10
|
+
- `paths.py` - Runtime path resolution for npm/local/Docker installs
|
|
10
11
|
|
|
11
12
|
## Key Integration Points
|
|
12
13
|
- **NervousSystem** - Central event bus; all modules communicate via events
|
|
13
14
|
- **Self** - Coordinates: memory, heart, LLM, subconscious, input/output
|
|
15
|
+
- **Paths** - Keeps project runtime state under local `data/` instead of hard-coded `/app`
|
|
14
16
|
- Connects to subconscious for proactive messaging
|
|
15
17
|
- Emits: `message_received`, `send_text`, `send_voice_file`, `send_image`, `send_video`
|
|
16
18
|
|
package/core/paths.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core path helpers.
|
|
3
|
+
|
|
4
|
+
Alive-AI can run from Docker at /app or from an npm-created project folder.
|
|
5
|
+
All runtime state must resolve through this module so local installs never try
|
|
6
|
+
to write into a read-only container path.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def project_root() -> Path:
|
|
14
|
+
configured = os.environ.get("ALIVE_AI_ROOT")
|
|
15
|
+
if configured:
|
|
16
|
+
return Path(configured).expanduser().resolve()
|
|
17
|
+
return Path(__file__).parent.parent.resolve()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _resolve_under_root(value: str) -> Path:
|
|
21
|
+
path = Path(value).expanduser()
|
|
22
|
+
if not path.is_absolute():
|
|
23
|
+
path = project_root() / path
|
|
24
|
+
return path.resolve()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def data_dir() -> Path:
|
|
28
|
+
configured = os.environ.get("ALIVE_AI_DATA_PATH") or os.environ.get("DATA_PATH")
|
|
29
|
+
path = _resolve_under_root(configured) if configured else project_root() / "data"
|
|
30
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
return path
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def state_file(name: str) -> Path:
|
|
35
|
+
return data_dir() / name
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def media_dir(name: str) -> Path:
|
|
39
|
+
path = project_root() / name
|
|
40
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
return path
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def cache_dir(name: str = "") -> Path:
|
|
45
|
+
base = project_root() / ".cache"
|
|
46
|
+
path = base / name if name else base
|
|
47
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
return path
|
package/core/self.py
CHANGED
|
@@ -4,6 +4,7 @@ The AI's Self - coordinates everything via nervous system
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
|
+
import contextlib
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from .events import NervousSystem
|
|
9
10
|
from .config import Config
|
|
@@ -130,7 +131,47 @@ class Self:
|
|
|
130
131
|
print(f"[{name}] Ready!")
|
|
131
132
|
|
|
132
133
|
# Start listening
|
|
133
|
-
|
|
134
|
+
try:
|
|
135
|
+
await self._input.start()
|
|
136
|
+
finally:
|
|
137
|
+
await self.stop()
|
|
138
|
+
|
|
139
|
+
async def stop(self):
|
|
140
|
+
"""Stop background loops and close network clients."""
|
|
141
|
+
if self._hot_reload:
|
|
142
|
+
with contextlib.suppress(Exception):
|
|
143
|
+
self._hot_reload.stop()
|
|
144
|
+
self._hot_reload = None
|
|
145
|
+
|
|
146
|
+
if self._input and hasattr(self._input, "stop"):
|
|
147
|
+
with contextlib.suppress(Exception):
|
|
148
|
+
result = self._input.stop()
|
|
149
|
+
if asyncio.iscoroutine(result):
|
|
150
|
+
await result
|
|
151
|
+
|
|
152
|
+
if self._default_mode:
|
|
153
|
+
with contextlib.suppress(Exception):
|
|
154
|
+
await self._default_mode.stop_background_processing()
|
|
155
|
+
self._default_mode = None
|
|
156
|
+
|
|
157
|
+
if self._subconscious:
|
|
158
|
+
with contextlib.suppress(Exception):
|
|
159
|
+
await self._subconscious.stop()
|
|
160
|
+
self._subconscious = None
|
|
161
|
+
|
|
162
|
+
if self._timer_task:
|
|
163
|
+
self._timer_task.cancel()
|
|
164
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
165
|
+
await self._timer_task
|
|
166
|
+
self._timer_task = None
|
|
167
|
+
|
|
168
|
+
seen = set()
|
|
169
|
+
for client in (self._llm, self._fast_llm):
|
|
170
|
+
if not client or id(client) in seen or not hasattr(client, "close"):
|
|
171
|
+
continue
|
|
172
|
+
seen.add(id(client))
|
|
173
|
+
with contextlib.suppress(Exception):
|
|
174
|
+
await client.close()
|
|
134
175
|
|
|
135
176
|
async def _decay_timer(self):
|
|
136
177
|
"""Natural emotion decay every minute + memory check"""
|
package/core/user_manager.py
CHANGED
|
@@ -8,6 +8,7 @@ import json
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Dict, Optional, Any
|
|
10
10
|
from datetime import datetime
|
|
11
|
+
from .paths import data_dir
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class UserManager:
|
|
@@ -26,10 +27,7 @@ class UserManager:
|
|
|
26
27
|
if base_path:
|
|
27
28
|
self.base_path = base_path
|
|
28
29
|
else:
|
|
29
|
-
|
|
30
|
-
docker_path = Path("/app/data")
|
|
31
|
-
local_path = Path(__file__).parent.parent / "data"
|
|
32
|
-
self.base_path = docker_path if docker_path.exists() else local_path
|
|
30
|
+
self.base_path = data_dir()
|
|
33
31
|
|
|
34
32
|
self.users_path = self.base_path / "users"
|
|
35
33
|
self.users_path.mkdir(parents=True, exist_ok=True)
|
package/docs/index.html
CHANGED
|
@@ -283,15 +283,16 @@ npx . chat</code></pre>
|
|
|
283
283
|
<section class="facts">
|
|
284
284
|
<div class="fact"><h2>Persistent affect</h2><p>Emotions decay, compound, and influence future messages instead of resetting after each prompt.</p></div>
|
|
285
285
|
<div class="fact"><h2>Memory with weight</h2><p>Built-in local memory stays active, and OpenMind can add cloud or local semantic recall.</p></div>
|
|
286
|
-
<div class="fact"><h2>Terminal or Telegram</h2><p>Chat from the
|
|
286
|
+
<div class="fact"><h2>Terminal or Telegram</h2><p>Chat from the split-pane terminal with `npx . chat`, use `npx . chat --plain` for raw mode, or connect Telegram for mobile access.</p></div>
|
|
287
287
|
</section>
|
|
288
288
|
|
|
289
289
|
<section class="section" id="setup">
|
|
290
290
|
<h2>Setup Flow</h2>
|
|
291
291
|
<p>`npx . setup` asks for the minimum required configuration and lets optional systems be skipped. Use `local` for Ollama, `skip` for optional keys, and OpenMind cloud or local when you want a shared long-term semantic memory.</p>
|
|
292
292
|
<div class="steps">
|
|
293
|
-
<div class="step"><strong>Start local terminal chat</strong><span>`npx . chat` starts the same runtime with
|
|
294
|
-
<div class="step"><strong>Start Telegram/runtime mode</strong><span>`npx . start` starts the
|
|
293
|
+
<div class="step"><strong>Start local terminal chat</strong><span>`npx . chat` starts the same runtime with chat on the left, logs on the right, and the WebUI at `http://127.0.0.1:8080`.</span></div>
|
|
294
|
+
<div class="step"><strong>Start Telegram/runtime mode</strong><span>`npx . start` starts the configured input channel and validates Telegram before polling. Stop foreground runs with `Ctrl+C`.</span></div>
|
|
295
|
+
<div class="step"><strong>Keep it updated</strong><span>`start` and `chat` check npm for newer versions. Use `npx . update` manually or `npx . uninstall` to remove local runtime files.</span></div>
|
|
295
296
|
<div class="step"><strong>Use OpenMind</strong><span>Choose `openmind-cloud` for `https://theopenmind.pro` or `openmind-local` for `http://127.0.0.1:3333`.</span></div>
|
|
296
297
|
<div class="step"><strong>Preview the dashboard</strong><span>`npx . demo` is keyless. The real runtime dashboard streams local state over SSE.</span></div>
|
|
297
298
|
</div>
|
package/heart/emotional_state.py
CHANGED
|
@@ -4,9 +4,9 @@ Extended for Soul Architecture dimensions
|
|
|
4
4
|
"""
|
|
5
5
|
import json
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from
|
|
7
|
+
from core.paths import state_file
|
|
8
8
|
|
|
9
|
-
EMOTION_STATE_PATH =
|
|
9
|
+
EMOTION_STATE_PATH = state_file("emotion_state.json")
|
|
10
10
|
DEFAULTS = {
|
|
11
11
|
"valence": 0.5, "arousal": 0.3, "dominance": 0.5, "desire": 0.0,
|
|
12
12
|
"high_desire_threshold": 0.7, "joy": 0.5, "love": 0.2, "trust": 0.5,
|
package/heart/hormonal.py
CHANGED
|
@@ -11,8 +11,8 @@ interprets and responds to the world.
|
|
|
11
11
|
from datetime import datetime, timedelta
|
|
12
12
|
from dataclasses import dataclass, field
|
|
13
13
|
from typing import Dict, List, Optional, Tuple
|
|
14
|
-
from pathlib import Path
|
|
15
14
|
import json
|
|
15
|
+
from core.paths import state_file
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@dataclass
|
|
@@ -61,7 +61,7 @@ class HormonalModulationMatrix:
|
|
|
61
61
|
MAX_METABOLITES = 10
|
|
62
62
|
|
|
63
63
|
# Persistence
|
|
64
|
-
HORMONAL_DATA_PATH =
|
|
64
|
+
HORMONAL_DATA_PATH = state_file("hormonal_state.json")
|
|
65
65
|
|
|
66
66
|
def __init__(self):
|
|
67
67
|
# Primary hormones (0.0 - 1.0)
|
package/heart/integrity.py
CHANGED
|
@@ -12,6 +12,7 @@ from typing import Optional, List, Dict, Any
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
import json
|
|
14
14
|
import os
|
|
15
|
+
from core.paths import data_dir
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@dataclass
|
|
@@ -118,7 +119,7 @@ class SelfIntegrityCore:
|
|
|
118
119
|
DAMAGE_RATE = 0.12 # Base damage from negative experiences
|
|
119
120
|
|
|
120
121
|
# Data persistence - configurable via environment variable
|
|
121
|
-
INTEGRITY_DATA_PATH =
|
|
122
|
+
INTEGRITY_DATA_PATH = data_dir() / "integrity_state.json"
|
|
122
123
|
|
|
123
124
|
def __init__(self):
|
|
124
125
|
# Core integrity components (0.0 - 1.0)
|
package/heart/interoception.py
CHANGED
|
@@ -31,16 +31,13 @@ import logging
|
|
|
31
31
|
import sys
|
|
32
32
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
33
33
|
from core.settings import get_float, get_int, get
|
|
34
|
+
from core.paths import state_file
|
|
34
35
|
|
|
35
36
|
# Configure logging
|
|
36
37
|
logger = logging.getLogger("Interoception")
|
|
37
38
|
logger.setLevel(logging.DEBUG)
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
if Path("/app/data").exists():
|
|
41
|
-
INTEROCEPTION_DATA_PATH = Path("/app/data/interoceptive_state.json")
|
|
42
|
-
else:
|
|
43
|
-
INTEROCEPTION_DATA_PATH = Path(__file__).parent.parent / "data" / "interoceptive_state.json"
|
|
40
|
+
INTEROCEPTION_DATA_PATH = state_file("interoceptive_state.json")
|
|
44
41
|
|
|
45
42
|
|
|
46
43
|
class InteroceptiveStateType(Enum):
|
package/heart/love.py
CHANGED
|
@@ -4,11 +4,11 @@ Attachment system - can fall in love
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
|
-
from pathlib import Path
|
|
8
7
|
from datetime import datetime
|
|
8
|
+
from core.paths import state_file
|
|
9
9
|
|
|
10
10
|
# Persistence path for attachment state
|
|
11
|
-
ATTACHMENT_STATE_PATH =
|
|
11
|
+
ATTACHMENT_STATE_PATH = state_file("attachment_state.json")
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class AttachmentSystem:
|
package/heart/scars.py
CHANGED
|
@@ -10,9 +10,9 @@ behaviors that persist over time.
|
|
|
10
10
|
from datetime import datetime
|
|
11
11
|
from dataclasses import dataclass, field
|
|
12
12
|
from typing import Dict, List, Optional
|
|
13
|
-
from pathlib import Path
|
|
14
13
|
import json
|
|
15
14
|
import random
|
|
15
|
+
from core.paths import state_file
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@dataclass
|
|
@@ -99,7 +99,7 @@ class EmotionalScarSystem:
|
|
|
99
99
|
POSITIVE_HEALING_RATE = 0.05 # With positive experience
|
|
100
100
|
|
|
101
101
|
# Persistence
|
|
102
|
-
SCAR_DATA_PATH =
|
|
102
|
+
SCAR_DATA_PATH = state_file("emotional_scars.json")
|
|
103
103
|
|
|
104
104
|
# Wound types and their patterns
|
|
105
105
|
WOUND_PATTERNS = {
|
|
@@ -471,4 +471,3 @@ class EmotionalScarSystem:
|
|
|
471
471
|
"vulnerability_summary": self.get_vulnerability_summary(),
|
|
472
472
|
"scar_descriptions": self.get_scar_descriptions()[:3] # Top 3
|
|
473
473
|
}
|
|
474
|
-
|
package/heart/telemetry.py
CHANGED
|
@@ -17,6 +17,7 @@ from pathlib import Path
|
|
|
17
17
|
import json
|
|
18
18
|
import threading
|
|
19
19
|
import time
|
|
20
|
+
from core.paths import state_file
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
@dataclass
|
|
@@ -129,10 +130,7 @@ class SoulTelemetry:
|
|
|
129
130
|
"""
|
|
130
131
|
|
|
131
132
|
# Default data path
|
|
132
|
-
DEFAULT_DATA_PATH =
|
|
133
|
-
|
|
134
|
-
# Alternative path for local development
|
|
135
|
-
FALLBACK_DATA_PATH = Path(__file__).parent.parent / "data" / "soul_telemetry.json"
|
|
133
|
+
DEFAULT_DATA_PATH = state_file("soul_telemetry.json")
|
|
136
134
|
|
|
137
135
|
# Default retention period (hours)
|
|
138
136
|
DEFAULT_RETENTION_HOURS = 24
|
|
@@ -160,11 +158,7 @@ class SoulTelemetry:
|
|
|
160
158
|
if data_path:
|
|
161
159
|
self.data_path = Path(data_path)
|
|
162
160
|
else:
|
|
163
|
-
|
|
164
|
-
if self.DEFAULT_DATA_PATH.parent.exists():
|
|
165
|
-
self.data_path = self.DEFAULT_DATA_PATH
|
|
166
|
-
else:
|
|
167
|
-
self.data_path = self.FALLBACK_DATA_PATH
|
|
161
|
+
self.data_path = self.DEFAULT_DATA_PATH
|
|
168
162
|
|
|
169
163
|
# Ensure data directory exists
|
|
170
164
|
self.data_path.parent.mkdir(parents=True, exist_ok=True)
|
package/input/manifest.md
CHANGED
|
@@ -8,7 +8,7 @@ How the AI perceives and processes incoming messages.
|
|
|
8
8
|
- `voice.py` - Voice message processing via STT
|
|
9
9
|
- `commands.py` - Admin command handler
|
|
10
10
|
- `terminal/` - Local terminal chat integration
|
|
11
|
-
- `listener.py` - CLI chat loop, slash commands, and output handlers
|
|
11
|
+
- `listener.py` - CLI chat loop, slash commands, TUI events, and output handlers
|
|
12
12
|
|
|
13
13
|
## Commands
|
|
14
14
|
- `/start` - Welcome message
|
|
@@ -24,7 +24,7 @@ How the AI perceives and processes incoming messages.
|
|
|
24
24
|
|
|
25
25
|
## Integration Points
|
|
26
26
|
- Receives Telegram updates
|
|
27
|
-
- Receives terminal input through `npx . chat`
|
|
27
|
+
- Receives split-pane terminal input through `npx . chat`, or raw shell input through `npx . chat --plain`
|
|
28
28
|
- Uses STT for voice transcription
|
|
29
29
|
- Emits `message_received` event to nervous system
|
|
30
30
|
- Commands access: heart, subconscious, LLM, voice, photos, videos
|