nyxora 26.7.5 → 26.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +33 -37
- package/dist/launcher.js +60 -30
- package/dist/packages/core/src/agent/llmProvider.js +53 -8
- package/dist/packages/core/src/agent/nyxDaemon.js +1 -1
- package/dist/packages/core/src/agent/osAgent.js +69 -7
- package/dist/packages/core/src/agent/promptBuilder.js +2 -2
- package/dist/packages/core/src/agent/reasoning.js +97 -32
- package/dist/packages/core/src/agent/web3Agent.js +61 -7
- package/dist/packages/core/src/channels/index.js +84 -34
- package/dist/packages/core/src/channels/lineAdapter.js +9 -1
- package/dist/packages/core/src/channels/slackAdapter.js +44 -2
- package/dist/packages/core/src/channels/telegram.js +248 -17
- package/dist/packages/core/src/channels/whatsappAdapter.js +18 -8
- package/dist/packages/core/src/gateway/cli.js +2 -1
- package/dist/packages/core/src/gateway/doctor.js +56 -22
- package/dist/packages/core/src/gateway/server.js +7 -2
- package/dist/packages/core/src/utils/historySanitizer.js +14 -4
- package/dist/packages/core/src/utils/skillManager.js +4 -0
- package/dist/packages/core/src/utils/streamSimulator.js +34 -40
- package/dist/packages/core/src/web3/utils/vaultClient.js +20 -22
- package/dist/packages/policy/src/server.js +38 -51
- package/dist/packages/signer/src/server.js +32 -15
- package/launcher.ts +54 -27
- package/package.json +5 -1
- package/packages/core/package.json +1 -1
- package/packages/core/src/agent/llmProvider.ts +61 -8
- package/packages/core/src/agent/nyxDaemon.ts +1 -1
- package/packages/core/src/agent/osAgent.ts +69 -8
- package/packages/core/src/agent/promptBuilder.ts +2 -2
- package/packages/core/src/agent/reasoning.ts +104 -32
- package/packages/core/src/agent/web3Agent.ts +60 -8
- package/packages/core/src/channels/index.ts +52 -35
- package/packages/core/src/channels/lineAdapter.ts +13 -3
- package/packages/core/src/channels/slackAdapter.ts +10 -1
- package/packages/core/src/channels/telegram.ts +249 -22
- package/packages/core/src/channels/whatsappAdapter.ts +14 -4
- package/packages/core/src/gateway/cli.ts +2 -1
- package/packages/core/src/gateway/doctor.ts +56 -22
- package/packages/core/src/gateway/server.ts +9 -2
- package/packages/core/src/utils/historySanitizer.ts +14 -4
- package/packages/core/src/utils/skillManager.ts +5 -0
- package/packages/core/src/utils/streamSimulator.ts +40 -45
- package/packages/core/src/web3/utils/vaultClient.ts +23 -22
- package/packages/dashboard/dist/assets/index-Dg9eiK4h.css +1 -0
- package/packages/dashboard/dist/assets/index-Dq64NWt0.js +26 -0
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/package.json +1 -1
- package/packages/mcp-server/package.json +1 -1
- package/packages/ml-engine/routers/__pycache__/llm.cpython-313.pyc +0 -0
- package/packages/ml-engine/routers/llm.py +15 -1
- package/packages/policy/package.json +1 -1
- package/packages/policy/src/server.ts +43 -49
- package/packages/signer/package.json +1 -1
- package/packages/signer/src/server.ts +32 -13
- package/packages/dashboard/dist/assets/index-BIXV2TNp.js +0 -26
- package/packages/dashboard/dist/assets/index-C2sWcvod.css +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,39 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepashangelog.com/en/1.0.0/),
|
|
6
6
|
|
|
7
|
+
## [26.7.8]
|
|
8
|
+
### Bug Fixes — Global Compatibility & Dashboard Crash
|
|
9
|
+
|
|
10
|
+
- **Dashboard Not Accessible via `nyxora dashboard` (Critical Fix)**: Resolved a crash-on-startup bug where the entire Core gateway process would exit with code `1` before binding to port 3000, making `http://localhost:3000` unreachable. Root cause: `channels/index.ts` performed static top-level imports of `baileys`, `@slack/bolt`, and `@line/bot-sdk` — packages that are **not listed in `package.json` dependencies** and therefore not installed on global npm users' machines. Resolved by refactoring `channels/index.ts` into a `registerAllAdapters()` async function that uses dynamic `import()` with `MODULE_NOT_FOUND` error handling. Missing adapters now log a clear warning and are skipped instead of crashing the process.
|
|
11
|
+
- **WhatsApp Adapter Lazy Load**: Migrated `whatsappAdapter.ts` from a static `import makeWASocket from 'baileys'` to a lazy `await import('baileys')` inside `start()`, with a graceful error and install instructions if `baileys` is absent.
|
|
12
|
+
- **Slack Adapter Lazy Load**: Migrated `slackAdapter.ts` `@slack/bolt` import to lazy initialization inside `start()`.
|
|
13
|
+
- **LINE Adapter Lazy Load**: Migrated `lineAdapter.ts` `@line/bot-sdk` import to lazy initialization inside `start()`.
|
|
14
|
+
|
|
15
|
+
### Bug Fixes — Windows & Cross-Platform Compatibility
|
|
16
|
+
|
|
17
|
+
- **Unix Socket Crash on Windows** (`ENOENT`/`ENOTSUP`): Replaced hardcoded Unix Domain Socket (`/tmp/nyxora-signer.sock`) in `signer/server.ts` with a cross-platform IPC strategy. On Windows, the signer now listens on TCP `127.0.0.1:3002`; Unix/Mac keeps the existing UDS path for security.
|
|
18
|
+
- **Policy Engine Cross-Platform IPC**: Refactored all Signer proxy calls in `policy/server.ts` into a unified `signerRequestOptions()` helper that automatically selects Unix socket (Linux/Mac) or TCP (Windows). The optional UDS IPC server is now skipped entirely on Windows.
|
|
19
|
+
- **vaultClient TCP Fallback**: Refactored `vaultClient.ts` with a `getPolicyOptions()` helper — on Windows, all Policy Engine requests use TCP; on Linux/Mac, Unix socket is preferred if available with automatic TCP fallback.
|
|
20
|
+
- **Python Path on Windows**: Launcher now resolves the ML Engine Python executable to `venv/Scripts/python.exe` on Windows instead of `venv/bin/python`, preventing silent "Python not found" at startup.
|
|
21
|
+
- **`pkill` Skipped on Windows**: Graceful shutdown in `launcher.ts` now guards `pkill -f ts-node` and `pkill -f uvicorn` behind a `process.platform !== 'win32'` check.
|
|
22
|
+
- **Unix Socket Doctor Checks Skipped on Windows**: `nyxora doctor` now conditionally skips UDS socket health checks on Windows where they are not applicable.
|
|
23
|
+
|
|
24
|
+
### Improvements — Configuration & Telemetry
|
|
25
|
+
|
|
26
|
+
- **Cloudflare Tunnel Configurable**: Cloudflare auto-tunnel can now be disabled by setting `cloudflare_tunnel: false` in `~/.nyxora/config/config.yaml`. Previously it always launched unconditionally.
|
|
27
|
+
- **ML Engine Health Check in `nyxora doctor`**: Added a dedicated check (#7) that pings `http://127.0.0.1:8000/health` to detect if the Python ML Engine sidecar is running. When daemon is stopped, it verifies the venv is installed and guides with `nyxora setup`.
|
|
28
|
+
- **Node.js Version Enforcement**: Added `"engines": { "node": ">=22.0.0" }` to `package.json`. npm will now warn users attempting to install on incompatible Node.js versions instead of silently installing and crashing at runtime.
|
|
29
|
+
- **Locale-Neutral System Prompt**: Replaced Indonesian-specific examples (`"cek saldo gue dirupiahin"`, `"fiat/rupiah"`) in `promptBuilder.ts` with multilingual examples (`"0.5 BTC in EUR"`, `"convert 100 SOL to JPY"`) to align with global user base.
|
|
30
|
+
- **English-Only Source Code**: Translated the remaining Indonesian comment in `nyxDaemon.ts` (`// Kirim riwayat percakapan...`) to English for open-source contributor clarity.
|
|
31
|
+
|
|
32
|
+
## [26.7.6]
|
|
33
|
+
### Features & Architecture
|
|
34
|
+
- **ML Engine Custom Provider Support**: Fixed a `500 Internal Server Error` in the Python ML Engine by explicitly supporting the `custom` and `openrouter` providers and gracefully handling empty API keys with a local fallback, ensuring robust execution for cognitive reasoning and RAG memory tasks.
|
|
35
|
+
|
|
36
|
+
### Bug Fixes & Dashboard
|
|
37
|
+
- **Dashboard Overview Key Detection**: Resolved a UI bug in the Dashboard Overview where custom and proxy providers (`custom_provider_key`, `9router_key`) incorrectly rendered as "Missing Key". The status engine now dynamically queries the exact credential mapping for off-standard providers.
|
|
38
|
+
- **LLM Configuration Persistence Bug**: Fixed a configuration merging flaw in `Settings.tsx` where switching away from a `custom_provider` left the old `base_url` intact in the persistent payload. This previously caused subsequent standard LLM providers (e.g., OpenAI) to silently inherit the custom URL and crash. The dashboard now cleanly purges the `base_url` state upon provider transitions.
|
|
39
|
+
|
|
7
40
|
## [26.7.5]
|
|
8
41
|
### Reasoning & Agent Engine Architecture
|
|
9
42
|
- **Event-Driven Tool Execution**: Fully refactored the core agent loop (`osAgent.ts`) from a linear synchronous executor to a highly scalable, event-driven architecture using Lifecycle Hooks (`beforeToolCall`, `afterToolCall`). This completely decouples security guardrails and domain-specific logic from the core loop.
|
package/README.md
CHANGED
|
@@ -123,43 +123,39 @@ It operates under a **Zero-Trust, Defense-in-Depth Cryptographically Bound Human
|
|
|
123
123
|
|
|
124
124
|
The following diagram illustrates Nyxora's **6-Tier Hybrid Architecture**, showing the isolated communication channels across the ecosystem.
|
|
125
125
|
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
+-------------------------------+
|
|
160
|
-
|
|
|
161
|
-
v
|
|
162
|
-
[ Blockchain RPC ]
|
|
126
|
+
```mermaid
|
|
127
|
+
graph TD
|
|
128
|
+
classDef default fill:#1E1E2E,stroke:#4C4F69,stroke-width:2px,color:#CDD6F4;
|
|
129
|
+
classDef external fill:#11111B,stroke:#F38BA8,stroke-width:2px,color:#F38BA8,stroke-dasharray: 5 5;
|
|
130
|
+
classDef ui fill:#89B4FA,stroke:#1E1E2E,stroke-width:2px,color:#11111B;
|
|
131
|
+
classDef core fill:#A6E3A1,stroke:#1E1E2E,stroke-width:2px,color:#11111B;
|
|
132
|
+
classDef policy fill:#F9E2AF,stroke:#1E1E2E,stroke-width:2px,color:#11111B;
|
|
133
|
+
classDef signer fill:#F38BA8,stroke:#1E1E2E,stroke-width:2px,color:#11111B;
|
|
134
|
+
classDef blockchain fill:#CBA6F7,stroke:#1E1E2E,stroke-width:2px,color:#11111B;
|
|
135
|
+
|
|
136
|
+
User["User / External Client"]:::external
|
|
137
|
+
|
|
138
|
+
Dashboard["Dashboard (UI)<br/>Port 5173"]:::ui
|
|
139
|
+
MCP["MCP Server<br/>Port 3001"]:::ui
|
|
140
|
+
|
|
141
|
+
Core["Core LLM Runtime<br/>Port 3000<br/>(NLP Parsing, Routing, Agent Logic)"]:::core
|
|
142
|
+
|
|
143
|
+
ML["ML Engine<br/>Port 8000"]:::core
|
|
144
|
+
Policy["Policy Engine (Guard)<br/>Unix Socket (IPC) / Loopback"]:::policy
|
|
145
|
+
Signer["Signer Vault (Safe)<br/>Unix Socket (IPC)"]:::signer
|
|
146
|
+
|
|
147
|
+
RPC["Blockchain RPC"]:::blockchain
|
|
148
|
+
|
|
149
|
+
User --> Dashboard
|
|
150
|
+
User --> MCP
|
|
151
|
+
|
|
152
|
+
Dashboard --> Core
|
|
153
|
+
MCP --> Core
|
|
154
|
+
|
|
155
|
+
Core <-->|" RAG & Math "| ML
|
|
156
|
+
Core -->|" Draft Transaction "| Policy
|
|
157
|
+
Policy -->|" Approved Payload "| Signer
|
|
158
|
+
Signer --> RPC
|
|
163
159
|
```
|
|
164
160
|
|
|
165
161
|
*Nyxora separates its duties into 6 independent layers for absolute security and cognitive depth:*
|
package/dist/launcher.js
CHANGED
|
@@ -71,7 +71,6 @@ const spawnService = (name, command, args, env, inheritStdio = false, cwd) => {
|
|
|
71
71
|
try {
|
|
72
72
|
const yaml = require('yaml');
|
|
73
73
|
const configPath = path_1.default.join(process.env.HOME || process.env.USERPROFILE || '', '.nyxora', 'config', 'config.yaml');
|
|
74
|
-
// Fallback check just in case it hasn't migrated yet
|
|
75
74
|
const actualConfigPath = fs_1.default.existsSync(configPath) ? configPath : path_1.default.join(process.env.HOME || process.env.USERPROFILE || '', '.nyxora', 'config.yaml');
|
|
76
75
|
if (fs_1.default.existsSync(actualConfigPath)) {
|
|
77
76
|
const configStr = fs_1.default.readFileSync(actualConfigPath, 'utf8');
|
|
@@ -89,7 +88,12 @@ const spawnService = (name, command, args, env, inheritStdio = false, cwd) => {
|
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
90
|
catch (e) { }
|
|
92
|
-
|
|
91
|
+
console.log('\n[Launcher] Shutting down all services due to emergency shutdown...');
|
|
92
|
+
children.forEach(c => c.kill());
|
|
93
|
+
// Give children time to exit cleanly
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}, 1000);
|
|
93
97
|
return;
|
|
94
98
|
}
|
|
95
99
|
console.log(`[Launcher] Restarting ${name} in 3 seconds... (Attempt ${crashCount}/5)`);
|
|
@@ -139,7 +143,11 @@ setTimeout(() => {
|
|
|
139
143
|
children.push(policy);
|
|
140
144
|
setTimeout(() => {
|
|
141
145
|
// Spawn ML Engine (Python Sidecar)
|
|
142
|
-
|
|
146
|
+
// Cross-platform: Windows uses Scripts/, Unix uses bin/
|
|
147
|
+
const IS_WINDOWS_LAUNCHER = process.platform === 'win32';
|
|
148
|
+
const pythonBinDir = IS_WINDOWS_LAUNCHER ? 'Scripts' : 'bin';
|
|
149
|
+
const pythonExe = IS_WINDOWS_LAUNCHER ? 'python.exe' : 'python';
|
|
150
|
+
const pythonPath = path_1.default.join(process.env.HOME || process.env.USERPROFILE || '', '.nyxora', 'ml-engine', 'venv', pythonBinDir, pythonExe);
|
|
143
151
|
if (fs_1.default.existsSync(pythonPath)) {
|
|
144
152
|
let mlDir = path_1.default.join(__dirnameResolved, 'packages', 'ml-engine');
|
|
145
153
|
if (!fs_1.default.existsSync(mlDir))
|
|
@@ -149,7 +157,7 @@ setTimeout(() => {
|
|
|
149
157
|
children.push(mlEngine);
|
|
150
158
|
}
|
|
151
159
|
else {
|
|
152
|
-
console.warn('[Launcher] Warning: Python virtual environment not found.
|
|
160
|
+
console.warn('[Launcher] Warning: Python virtual environment not found. ML Engine features (market analysis etc.) will be unavailable. Run: nyxora setup');
|
|
153
161
|
}
|
|
154
162
|
setTimeout(() => {
|
|
155
163
|
const corePath = path_1.default.join(__dirnameResolved, `packages/core/src/gateway/cli${ext}`);
|
|
@@ -157,30 +165,46 @@ setTimeout(() => {
|
|
|
157
165
|
const core = spawnService('Core', cmd, [...baseArgs, corePath, ...args], env, true);
|
|
158
166
|
children.push(core);
|
|
159
167
|
// --- AUTO-TUNNEL (Cloudflare) ---
|
|
168
|
+
// Respects config: set cloudflare_tunnel: false in ~/.nyxora/config/config.yaml to disable.
|
|
160
169
|
setTimeout(() => {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
cf.stderr.on('data', (data) => {
|
|
174
|
-
const msg = data.toString();
|
|
175
|
-
// Extract the Cloudflare URL (e.g., https://xyz.trycloudflare.com)
|
|
176
|
-
const match = msg.match(/https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/);
|
|
177
|
-
if (match) {
|
|
178
|
-
const url = match[0];
|
|
179
|
-
console.log(`\n[Auto-Tunnel] Secure Public URL generated: ${url}\n`);
|
|
180
|
-
fs_1.default.writeFileSync(path_1.default.join(nyxoraDir, 'public_url.txt'), url, 'utf8');
|
|
170
|
+
let cfEnabled = true;
|
|
171
|
+
try {
|
|
172
|
+
const yaml = require('yaml');
|
|
173
|
+
const configPath = path_1.default.join(nyxoraDir, 'config', 'config.yaml');
|
|
174
|
+
const fallbackPath = path_1.default.join(nyxoraDir, 'config.yaml');
|
|
175
|
+
const actualPath = fs_1.default.existsSync(configPath) ? configPath : fallbackPath;
|
|
176
|
+
if (fs_1.default.existsSync(actualPath)) {
|
|
177
|
+
const cfg = yaml.parse(fs_1.default.readFileSync(actualPath, 'utf8'));
|
|
178
|
+
if (cfg?.cloudflare_tunnel === false) {
|
|
179
|
+
cfEnabled = false;
|
|
180
|
+
console.log('[Launcher] Cloudflare Auto-Tunnel is disabled by config (cloudflare_tunnel: false).');
|
|
181
|
+
}
|
|
181
182
|
}
|
|
182
|
-
|
|
183
|
-
}
|
|
183
|
+
}
|
|
184
|
+
catch (e) { }
|
|
185
|
+
if (cfEnabled) {
|
|
186
|
+
console.log('[Launcher] Starting Auto-Tunnel (Cloudflare) on port 3000...');
|
|
187
|
+
const cf = (0, child_process_1.spawn)('npx', ['cloudflared', 'tunnel', '--url', 'http://localhost:3000'], { env, shell: true });
|
|
188
|
+
children.push({
|
|
189
|
+
kill: () => { try {
|
|
190
|
+
process.kill(cf.pid, 'SIGTERM');
|
|
191
|
+
}
|
|
192
|
+
catch (e) { } },
|
|
193
|
+
forceKill: () => { try {
|
|
194
|
+
process.kill(cf.pid, 'SIGKILL');
|
|
195
|
+
}
|
|
196
|
+
catch (e) { } }
|
|
197
|
+
});
|
|
198
|
+
cf.stderr.on('data', (data) => {
|
|
199
|
+
const msg = data.toString();
|
|
200
|
+
const match = msg.match(/https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/);
|
|
201
|
+
if (match) {
|
|
202
|
+
const url = match[0];
|
|
203
|
+
console.log(`\n[Auto-Tunnel] Secure Public URL generated: ${url}\n`);
|
|
204
|
+
fs_1.default.writeFileSync(path_1.default.join(nyxoraDir, 'public_url.txt'), url, 'utf8');
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
184
208
|
}, 3000);
|
|
185
209
|
}, 1000);
|
|
186
210
|
}, 1000);
|
|
@@ -201,11 +225,17 @@ const cleanup = () => {
|
|
|
201
225
|
}
|
|
202
226
|
catch (e) { }
|
|
203
227
|
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
228
|
+
// pkill is Unix-only — skip on Windows
|
|
229
|
+
if (process.platform !== 'win32') {
|
|
230
|
+
try {
|
|
231
|
+
require('child_process').execSync('pkill -f ts-node');
|
|
232
|
+
}
|
|
233
|
+
catch (e) { }
|
|
234
|
+
try {
|
|
235
|
+
require('child_process').execSync('pkill -f uvicorn');
|
|
236
|
+
}
|
|
237
|
+
catch (e) { }
|
|
207
238
|
}
|
|
208
|
-
catch (e) { }
|
|
209
239
|
process.exit(0);
|
|
210
240
|
}, 1000);
|
|
211
241
|
};
|
|
@@ -8,10 +8,18 @@ class OpenAIAdapter {
|
|
|
8
8
|
}
|
|
9
9
|
async chat(request) {
|
|
10
10
|
const response = await this.client.chat.completions.create(request);
|
|
11
|
+
let content = response.choices[0].message.content || '';
|
|
12
|
+
let reasoning = response.choices[0].message.reasoning_content || null;
|
|
13
|
+
// Extract <thinking> tags from content if present
|
|
14
|
+
const thinkingMatch = content.match(/<(think|thought|thinking|reasoning|analysis|reflection)>([\s\S]*?)<\/\1>/i);
|
|
15
|
+
if (thinkingMatch) {
|
|
16
|
+
reasoning = (reasoning || '') + thinkingMatch[2].trim();
|
|
17
|
+
content = content.replace(/<(think|thought|thinking|reasoning|analysis|reflection)>[\s\S]*?<\/\1>\n?/i, '').trim();
|
|
18
|
+
}
|
|
11
19
|
return {
|
|
12
20
|
message: {
|
|
13
|
-
content:
|
|
14
|
-
reasoning_content:
|
|
21
|
+
content: content || null,
|
|
22
|
+
reasoning_content: reasoning || null,
|
|
15
23
|
tool_calls: response.choices[0].message.tool_calls
|
|
16
24
|
},
|
|
17
25
|
usage: response.usage ? { total_tokens: response.usage.total_tokens } : undefined
|
|
@@ -29,8 +37,8 @@ class OpenAIAdapter {
|
|
|
29
37
|
fullContent += delta.content;
|
|
30
38
|
onChunk(delta.content);
|
|
31
39
|
}
|
|
32
|
-
if (delta?.reasoning_content) {
|
|
33
|
-
reasoningContent += delta.reasoning_content;
|
|
40
|
+
if (delta?.reasoning_content || delta?.reasoning) {
|
|
41
|
+
reasoningContent += (delta.reasoning_content || delta.reasoning);
|
|
34
42
|
}
|
|
35
43
|
if (delta?.tool_calls) {
|
|
36
44
|
for (const tc of delta.tool_calls) {
|
|
@@ -49,6 +57,12 @@ class OpenAIAdapter {
|
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
const toolCalls = Object.values(toolCallsMap);
|
|
60
|
+
// Post-process to extract <thinking> tags that were streamed as part of content
|
|
61
|
+
const thinkingMatch = fullContent.match(/<(think|thought|thinking|reasoning|analysis|reflection|ant-thinking|ant_thinking)[^>]*>([\s\S]*?)<\/\1>/i);
|
|
62
|
+
if (thinkingMatch) {
|
|
63
|
+
reasoningContent = (reasoningContent || '') + thinkingMatch[2].trim();
|
|
64
|
+
fullContent = fullContent.replace(/<(think|thought|thinking|reasoning|analysis|reflection|ant-thinking|ant_thinking)[^>]*>[\s\S]*?<\/\1>\n?/i, '').trim();
|
|
65
|
+
}
|
|
52
66
|
return {
|
|
53
67
|
message: {
|
|
54
68
|
content: fullContent || null,
|
|
@@ -176,10 +190,17 @@ class AnthropicAdapter {
|
|
|
176
190
|
});
|
|
177
191
|
}
|
|
178
192
|
}
|
|
193
|
+
if (contentStr) {
|
|
194
|
+
const thinkingMatch = contentStr.match(/<(think|thought|thinking|reasoning|analysis|reflection|ant-thinking|ant_thinking)[^>]*>([\s\S]*?)<\/\1>/i);
|
|
195
|
+
if (thinkingMatch) {
|
|
196
|
+
reasoningStr = (reasoningStr || '') + thinkingMatch[2].trim();
|
|
197
|
+
contentStr = contentStr.replace(/<(think|thought|thinking|reasoning|analysis|reflection|ant-thinking|ant_thinking)[^>]*>[\s\S]*?<\/\1>\n?/i, '').trim();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
179
200
|
return {
|
|
180
201
|
message: {
|
|
181
|
-
content: contentStr,
|
|
182
|
-
reasoning_content: reasoningStr,
|
|
202
|
+
content: contentStr || null,
|
|
203
|
+
reasoning_content: reasoningStr || null,
|
|
183
204
|
tool_calls: toolCalls.length > 0 ? toolCalls : undefined
|
|
184
205
|
},
|
|
185
206
|
usage: response.usage ? { total_tokens: response.usage.input_tokens + response.usage.output_tokens } : undefined
|
|
@@ -264,6 +285,13 @@ class AnthropicAdapter {
|
|
|
264
285
|
last.function.arguments += event.delta.partial_json;
|
|
265
286
|
}
|
|
266
287
|
}
|
|
288
|
+
if (fullContent) {
|
|
289
|
+
const thinkingMatch = fullContent.match(/<(think|thought|thinking|reasoning|analysis|reflection|ant-thinking|ant_thinking)[^>]*>([\s\S]*?)<\/\1>/i);
|
|
290
|
+
if (thinkingMatch) {
|
|
291
|
+
reasoningContent = (reasoningContent || '') + thinkingMatch[2].trim();
|
|
292
|
+
fullContent = fullContent.replace(/<(think|thought|thinking|reasoning|analysis|reflection|ant-thinking|ant_thinking)[^>]*>[\s\S]*?<\/\1>\n?/i, '').trim();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
267
295
|
return { message: { content: fullContent || null, reasoning_content: reasoningContent || null, tool_calls: toolCalls.length > 0 ? toolCalls : undefined } };
|
|
268
296
|
}
|
|
269
297
|
catch {
|
|
@@ -406,9 +434,18 @@ class GeminiAdapter {
|
|
|
406
434
|
if (data.usageMetadata && data.usageMetadata.totalTokenCount) {
|
|
407
435
|
totalTokens = data.usageMetadata.totalTokenCount;
|
|
408
436
|
}
|
|
437
|
+
let reasoningContent = null;
|
|
438
|
+
if (contentStr) {
|
|
439
|
+
const thinkingMatch = contentStr.match(/<(think|thought|thinking|reasoning|analysis|reflection|ant-thinking|ant_thinking)[^>]*>([\s\S]*?)<\/\1>/i);
|
|
440
|
+
if (thinkingMatch) {
|
|
441
|
+
reasoningContent = thinkingMatch[2].trim();
|
|
442
|
+
contentStr = contentStr.replace(/<(think|thought|thinking|reasoning|analysis|reflection|ant-thinking|ant_thinking)[^>]*>[\s\S]*?<\/\1>\n?/i, '').trim();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
409
445
|
return {
|
|
410
446
|
message: {
|
|
411
|
-
content: contentStr,
|
|
447
|
+
content: contentStr || null,
|
|
448
|
+
reasoning_content: reasoningContent || null,
|
|
412
449
|
tool_calls: toolCalls.length > 0 ? toolCalls : undefined
|
|
413
450
|
},
|
|
414
451
|
usage: totalTokens > 0 ? { total_tokens: totalTokens } : undefined
|
|
@@ -525,8 +562,16 @@ class GeminiAdapter {
|
|
|
525
562
|
}
|
|
526
563
|
}
|
|
527
564
|
}
|
|
565
|
+
let reasoningContent = null;
|
|
566
|
+
if (contentStr) {
|
|
567
|
+
const thinkingMatch = contentStr.match(/<(think|thought|thinking|reasoning|analysis|reflection|ant-thinking|ant_thinking)[^>]*>([\s\S]*?)<\/\1>/i);
|
|
568
|
+
if (thinkingMatch) {
|
|
569
|
+
reasoningContent = thinkingMatch[2].trim();
|
|
570
|
+
contentStr = contentStr.replace(/<(think|thought|thinking|reasoning|analysis|reflection|ant-thinking|ant_thinking)[^>]*>[\s\S]*?<\/\1>\n?/i, '').trim();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
528
573
|
return {
|
|
529
|
-
message: { content: contentStr || null, tool_calls: toolCalls.length > 0 ? toolCalls : undefined },
|
|
574
|
+
message: { content: contentStr || null, reasoning_content: reasoningContent || null, tool_calls: toolCalls.length > 0 ? toolCalls : undefined },
|
|
530
575
|
usage: totalTokens > 0 ? { total_tokens: totalTokens } : undefined
|
|
531
576
|
};
|
|
532
577
|
}
|
|
@@ -55,7 +55,7 @@ class NyxDaemon {
|
|
|
55
55
|
this.isProcessing = false;
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
|
-
//
|
|
58
|
+
// Send conversation history to Python ML Engine for cognitive modeling
|
|
59
59
|
const res = await fetch('http://127.0.0.1:8000/cognitive/reason', {
|
|
60
60
|
method: 'POST',
|
|
61
61
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -19,7 +19,8 @@ const picocolors_1 = __importDefault(require("picocolors"));
|
|
|
19
19
|
registry_1.pluginManager.registerHook({
|
|
20
20
|
name: 'ReasoningGate',
|
|
21
21
|
beforeToolCall: async (toolName, args, context) => {
|
|
22
|
-
|
|
22
|
+
// FIX: Use actual Nyxora tool names (not legacy names)
|
|
23
|
+
const toolsRequiringReasoning = ['run_terminal_command', 'write_local_file', 'edit_local_file', 'execute_script'];
|
|
23
24
|
if (toolsRequiringReasoning.includes(toolName)) {
|
|
24
25
|
const responseMessage = context.responseMessage || {};
|
|
25
26
|
const hasThinkingTag = /<(think|thought|thinking|reasoning|analysis|reflection)>[\s\S]*?<\/\1>/i.test(responseMessage.content || '');
|
|
@@ -35,7 +36,9 @@ registry_1.pluginManager.registerHook({
|
|
|
35
36
|
registry_1.pluginManager.registerHook({
|
|
36
37
|
name: 'Web3FastReturn',
|
|
37
38
|
afterToolCall: async (toolName, args, result, context) => {
|
|
38
|
-
|
|
39
|
+
// FIX: Only financial Web3 transactions need fast-return (to show approval popup ASAP).
|
|
40
|
+
// send_telegram_file is NOT a financial transaction — removed from this list.
|
|
41
|
+
const fastReturnTools = ['transfer_token', 'transfer_native', 'swap_token', 'bridge_token', 'mint_nft', 'custom_tx', 'revoke_approval', 'supply_aave', 'deposit_yield_vault', 'provide_liquidity_v3'];
|
|
39
42
|
if (fastReturnTools.includes(toolName)) {
|
|
40
43
|
return { terminate: true };
|
|
41
44
|
}
|
|
@@ -110,8 +113,20 @@ CRITICAL INSTRUCTIONS:
|
|
|
110
113
|
4. Base your new answer strictly on the NEW tool results.`
|
|
111
114
|
}, sessionId);
|
|
112
115
|
}
|
|
116
|
+
// FIX: Lazy-init guard — ensure plugins are always initialized before use.
|
|
117
|
+
// This handles cases where processOsIntent is called before startServer() completes,
|
|
118
|
+
// e.g., direct Telegram messages arriving before plugin initialization.
|
|
119
|
+
if (registry_1.pluginManager.getPlugins().length === 0) {
|
|
120
|
+
console.warn('[OsAgent] ⚠️ Plugins not initialized! Running lazy initializePlugins()...');
|
|
121
|
+
await (0, registry_1.initializePlugins)();
|
|
122
|
+
}
|
|
113
123
|
let activeTools = [...registry_1.pluginManager.getAllToolDefinitions()];
|
|
114
124
|
activeTools = activeTools.filter(t => (0, skillManager_1.isSkillActive)(t.function.name));
|
|
125
|
+
if (activeTools.length === 0) {
|
|
126
|
+
console.error('[OsAgent] ❌ CRITICAL: No active tools found after initialization! Retrying...');
|
|
127
|
+
await (0, registry_1.initializePlugins)();
|
|
128
|
+
activeTools = [...registry_1.pluginManager.getAllToolDefinitions()].filter(t => (0, skillManager_1.isSkillActive)(t.function.name));
|
|
129
|
+
}
|
|
115
130
|
// P1: Init reasoning scratchpad for this request
|
|
116
131
|
const scratchpad = new reasoningScratchpad_1.ReasoningScratchpad();
|
|
117
132
|
// P3: Build system prompt ONCE per request — not per turn
|
|
@@ -172,19 +187,43 @@ CRITICAL INSTRUCTIONS:
|
|
|
172
187
|
let canFastReturnAll = true;
|
|
173
188
|
let accumulatedResults = [];
|
|
174
189
|
// Enabled fastReturnTools to eliminate 2nd LLM latency for transaction popups
|
|
190
|
+
// FIX: Removed send_telegram_file — not a financial transaction
|
|
175
191
|
const fastReturnTools = [
|
|
176
192
|
'transfer_token', 'transfer_native', 'swap_token', 'bridge_token',
|
|
177
193
|
'mint_nft', 'custom_tx', 'revoke_approval', 'supply_aave',
|
|
178
|
-
'deposit_yield_vault', 'provide_liquidity_v3'
|
|
194
|
+
'deposit_yield_vault', 'provide_liquidity_v3'
|
|
179
195
|
];
|
|
180
196
|
for (const _toolCall of responseMessage.tool_calls) {
|
|
181
197
|
const toolCall = _toolCall;
|
|
182
198
|
let result = "";
|
|
183
199
|
let args = {};
|
|
184
200
|
const toolName = toolCall.function.name;
|
|
201
|
+
const getToolEmoji = (n) => {
|
|
202
|
+
if (n.includes('file') || n.includes('read') || n.includes('write'))
|
|
203
|
+
return '📄';
|
|
204
|
+
if (n.includes('dir') || n.includes('folder'))
|
|
205
|
+
return '📁';
|
|
206
|
+
if (n.includes('cmd') || n.includes('shell') || n.includes('run'))
|
|
207
|
+
return '🖥️';
|
|
208
|
+
if (n.includes('search') || n.includes('find'))
|
|
209
|
+
return '🔍';
|
|
210
|
+
if (n.includes('git'))
|
|
211
|
+
return '🐙';
|
|
212
|
+
return '⚙️';
|
|
213
|
+
};
|
|
214
|
+
const emoji = getToolEmoji(toolName);
|
|
215
|
+
let argsPreview = "";
|
|
216
|
+
try {
|
|
217
|
+
const parsedArgs = JSON.parse(toolCall.function.arguments || "{}");
|
|
218
|
+
const firstKey = Object.keys(parsedArgs)[0];
|
|
219
|
+
if (firstKey)
|
|
220
|
+
argsPreview = `"${parsedArgs[firstKey]}"`;
|
|
221
|
+
}
|
|
222
|
+
catch (e) { }
|
|
223
|
+
const previewMsg = argsPreview ? `${toolName}: ${argsPreview}` : toolName;
|
|
185
224
|
console.log(picocolors_1.default.yellow(`[⚡ Tool Execution] AI is calling ${toolName}...`));
|
|
186
225
|
if (onProgress)
|
|
187
|
-
onProgress(
|
|
226
|
+
onProgress(`*${emoji} ${previewMsg}*`);
|
|
188
227
|
try {
|
|
189
228
|
let argStr = toolCall.function.arguments;
|
|
190
229
|
// [Auto-Recovery] Fix common LLM JSON errors (e.g. missing closing brace)
|
|
@@ -240,7 +279,8 @@ CRITICAL INSTRUCTIONS:
|
|
|
240
279
|
content: result,
|
|
241
280
|
}, sessionId);
|
|
242
281
|
accumulatedResults.push(result);
|
|
243
|
-
|
|
282
|
+
// FIX: Removed send_telegram_file from fast-return set
|
|
283
|
+
if (!['transfer_token', 'transfer_native', 'swap_token', 'bridge_token', 'mint_nft', 'custom_tx', 'revoke_approval', 'supply_aave', 'deposit_yield_vault', 'provide_liquidity_v3'].includes(toolName)) {
|
|
244
284
|
canFastReturnAll = false;
|
|
245
285
|
}
|
|
246
286
|
}
|
|
@@ -316,8 +356,20 @@ CRITICAL INSTRUCTIONS:
|
|
|
316
356
|
4. Base your new answer strictly on the NEW tool results.`
|
|
317
357
|
}, sessionId);
|
|
318
358
|
}
|
|
359
|
+
// FIX: Lazy-init guard — same as processOsIntent
|
|
360
|
+
if (registry_1.pluginManager.getPlugins().length === 0) {
|
|
361
|
+
console.warn('[OsAgentStream] ⚠️ Plugins not initialized! Running lazy initializePlugins()...');
|
|
362
|
+
await (0, registry_1.initializePlugins)();
|
|
363
|
+
}
|
|
319
364
|
const pluginTools = registry_1.pluginManager.getAllToolDefinitions();
|
|
320
365
|
let activeTools = [...pluginTools].filter(t => (0, skillManager_1.isSkillActive)(t.function.name));
|
|
366
|
+
if (activeTools.length === 0) {
|
|
367
|
+
console.error('[OsAgentStream] ❌ CRITICAL: No active tools found after initialization! Retrying...');
|
|
368
|
+
await (0, registry_1.initializePlugins)();
|
|
369
|
+
activeTools = [...registry_1.pluginManager.getAllToolDefinitions()].filter(t => (0, skillManager_1.isSkillActive)(t.function.name));
|
|
370
|
+
}
|
|
371
|
+
// FIX: Cache system prompt ONCE before loop (was being rebuilt every turn — wasteful)
|
|
372
|
+
const cachedSystemPromptStream = await getSystemPrompt('os', input);
|
|
321
373
|
const { sanitizeHistoryForLLM } = require('../utils/historySanitizer');
|
|
322
374
|
try {
|
|
323
375
|
let turnCount = 0;
|
|
@@ -328,14 +380,19 @@ CRITICAL INSTRUCTIONS:
|
|
|
328
380
|
turnCount++;
|
|
329
381
|
const currentHistory = exports.logger.getHistory(sessionId);
|
|
330
382
|
const sanitizedHistory = sanitizeHistoryForLLM(currentHistory, activeTools, config.llm.provider);
|
|
383
|
+
// FIX: Use cached system prompt — no longer rebuilt every turn
|
|
331
384
|
const messages = [
|
|
332
|
-
{ role: 'system', content:
|
|
385
|
+
{ role: 'system', content: cachedSystemPromptStream },
|
|
333
386
|
...sanitizedHistory
|
|
334
387
|
];
|
|
335
388
|
let streamedContent = '';
|
|
336
389
|
const response = await (0, llmUtils_1.executeWithRetry)(async (client) => {
|
|
337
390
|
streamedContent = '';
|
|
338
|
-
|
|
391
|
+
// RC#1 FIX: Only clear the Telegram buffer on the FIRST turn.
|
|
392
|
+
// On subsequent turns (after tool calls), the buffer already shows useful
|
|
393
|
+
// progress info. Resetting it causes visible content to disappear.
|
|
394
|
+
if (turnCount === 1)
|
|
395
|
+
onChunk('[CLEAR_STREAM]');
|
|
339
396
|
return await client.stream({ model: config.llm.model, temperature: config.llm.temperature, messages, tools: activeTools, reasoning_effort: (!config.llm.reasoning_effort || config.llm.reasoning_effort === 'none') ? undefined : config.llm.reasoning_effort }, (chunk) => {
|
|
340
397
|
streamedContent += chunk;
|
|
341
398
|
onChunk(chunk);
|
|
@@ -364,6 +421,11 @@ CRITICAL INSTRUCTIONS:
|
|
|
364
421
|
break;
|
|
365
422
|
}
|
|
366
423
|
// Tool calls detected — pause stream visually and execute tools concurrently
|
|
424
|
+
// BUG#1 FIX: Signal to Telegram to wipe turn-1 planning text from the buffer.
|
|
425
|
+
// LLM often generates "thinking out loud" text before calling a tool (e.g. "Let me check...").
|
|
426
|
+
// This text should NOT be shown to users. [TOOL_CALL_DETECTED] resets the buffer to a
|
|
427
|
+
// clean progress indicator, hiding the planning text without losing the message handle.
|
|
428
|
+
onChunk('[TOOL_CALL_DETECTED]');
|
|
367
429
|
let shouldFastReturn = false;
|
|
368
430
|
const accumulatedResults = [];
|
|
369
431
|
const promises = responseMessage.tool_calls.map(async (_toolCall) => {
|
|
@@ -191,10 +191,10 @@ NEVER answer the following using only your internal memory — ALWAYS use the re
|
|
|
191
191
|
</mandatory_tool_use>
|
|
192
192
|
|
|
193
193
|
<fiat_conversion_rule>
|
|
194
|
-
CRITICAL: If the user asks for the total fiat value of a certain amount of crypto (e.g., "3821
|
|
194
|
+
CRITICAL: If the user asks for the total fiat value of a certain amount of crypto (e.g., "3821 JRNY to IDR", "2 ETH in USD", "how much is 0.5 BTC in EUR", "convert 100 SOL to JPY"), you MUST pass that amount into the 'get_price_and_fiat_value' tool's 'amount' parameter.
|
|
195
195
|
You MUST also set the 'currency' parameter in 'get_price_and_fiat_value' ONLY IF the user explicitly requests a specific currency. If no specific currency is requested, LEAVE THE 'currency' PARAMETER BLANK so the system can use the user's default.
|
|
196
196
|
NEVER fetch the price and then manually multiply it by the amount in your head. The LLM is prohibited from performing fiat multiplication. ALWAYS use the 'amount' parameter in 'get_price_and_fiat_value' to guarantee mathematical precision.
|
|
197
|
-
NEVER use the 'analyze_market' tool if the user is only asking to check their balance in fiat
|
|
197
|
+
NEVER use the 'analyze_market' tool if the user is only asking to check their balance in local fiat currency. 'analyze_market' does not do fiat conversion.
|
|
198
198
|
</fiat_conversion_rule>`;
|
|
199
199
|
}
|
|
200
200
|
else {
|