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.
Files changed (57) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +33 -37
  3. package/dist/launcher.js +60 -30
  4. package/dist/packages/core/src/agent/llmProvider.js +53 -8
  5. package/dist/packages/core/src/agent/nyxDaemon.js +1 -1
  6. package/dist/packages/core/src/agent/osAgent.js +69 -7
  7. package/dist/packages/core/src/agent/promptBuilder.js +2 -2
  8. package/dist/packages/core/src/agent/reasoning.js +97 -32
  9. package/dist/packages/core/src/agent/web3Agent.js +61 -7
  10. package/dist/packages/core/src/channels/index.js +84 -34
  11. package/dist/packages/core/src/channels/lineAdapter.js +9 -1
  12. package/dist/packages/core/src/channels/slackAdapter.js +44 -2
  13. package/dist/packages/core/src/channels/telegram.js +248 -17
  14. package/dist/packages/core/src/channels/whatsappAdapter.js +18 -8
  15. package/dist/packages/core/src/gateway/cli.js +2 -1
  16. package/dist/packages/core/src/gateway/doctor.js +56 -22
  17. package/dist/packages/core/src/gateway/server.js +7 -2
  18. package/dist/packages/core/src/utils/historySanitizer.js +14 -4
  19. package/dist/packages/core/src/utils/skillManager.js +4 -0
  20. package/dist/packages/core/src/utils/streamSimulator.js +34 -40
  21. package/dist/packages/core/src/web3/utils/vaultClient.js +20 -22
  22. package/dist/packages/policy/src/server.js +38 -51
  23. package/dist/packages/signer/src/server.js +32 -15
  24. package/launcher.ts +54 -27
  25. package/package.json +5 -1
  26. package/packages/core/package.json +1 -1
  27. package/packages/core/src/agent/llmProvider.ts +61 -8
  28. package/packages/core/src/agent/nyxDaemon.ts +1 -1
  29. package/packages/core/src/agent/osAgent.ts +69 -8
  30. package/packages/core/src/agent/promptBuilder.ts +2 -2
  31. package/packages/core/src/agent/reasoning.ts +104 -32
  32. package/packages/core/src/agent/web3Agent.ts +60 -8
  33. package/packages/core/src/channels/index.ts +52 -35
  34. package/packages/core/src/channels/lineAdapter.ts +13 -3
  35. package/packages/core/src/channels/slackAdapter.ts +10 -1
  36. package/packages/core/src/channels/telegram.ts +249 -22
  37. package/packages/core/src/channels/whatsappAdapter.ts +14 -4
  38. package/packages/core/src/gateway/cli.ts +2 -1
  39. package/packages/core/src/gateway/doctor.ts +56 -22
  40. package/packages/core/src/gateway/server.ts +9 -2
  41. package/packages/core/src/utils/historySanitizer.ts +14 -4
  42. package/packages/core/src/utils/skillManager.ts +5 -0
  43. package/packages/core/src/utils/streamSimulator.ts +40 -45
  44. package/packages/core/src/web3/utils/vaultClient.ts +23 -22
  45. package/packages/dashboard/dist/assets/index-Dg9eiK4h.css +1 -0
  46. package/packages/dashboard/dist/assets/index-Dq64NWt0.js +26 -0
  47. package/packages/dashboard/dist/index.html +2 -2
  48. package/packages/dashboard/package.json +1 -1
  49. package/packages/mcp-server/package.json +1 -1
  50. package/packages/ml-engine/routers/__pycache__/llm.cpython-313.pyc +0 -0
  51. package/packages/ml-engine/routers/llm.py +15 -1
  52. package/packages/policy/package.json +1 -1
  53. package/packages/policy/src/server.ts +43 -49
  54. package/packages/signer/package.json +1 -1
  55. package/packages/signer/src/server.ts +32 -13
  56. package/packages/dashboard/dist/assets/index-BIXV2TNp.js +0 -26
  57. 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
- ```text
127
- +-------------------------------------------------------------+
128
- | Nyxora 6-Tier Architecture |
129
- +-------------------------------------------------------------+
130
-
131
- [ User / External Client ]
132
- |
133
- v
134
- +-----------------------------+ +-------------------------+
135
- | Dashboard (UI) | | MCP Server |
136
- | Port 5173 | | Port 3001 |
137
- +-----------------------------+ +-------------------------+
138
- | |
139
- +---------------+------------------+
140
- |
141
- v
142
- +--------------------+
143
- | Core LLM Runtime | <--- (NLP Parsing, Routing,
144
- | Port 3000 | Agent Logic)
145
- +--------------------+
146
- ^ |
147
- (RAG & Math) | | (Draft Transaction)
148
- v v
149
- +-------------------------+ +-------------------------------+
150
- | ML Engine | | Policy Engine (Guard) |
151
- | Port 8000 | | Unix Socket (IPC) / Loopback |
152
- +-------------------------+ +-------------------------------+
153
- |
154
- | (Approved Payload)
155
- v
156
- +-------------------------------+
157
- | Signer Vault (Safe) |
158
- | Unix Socket (IPC) |
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
- process.exit(1);
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
- const pythonPath = path_1.default.join(process.env.HOME || process.env.USERPROFILE || '', '.nyxora', 'ml-engine', 'venv', 'bin', 'python');
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. Did you run setup?');
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
- console.log('[Launcher] Starting Auto-Tunnel (Cloudflare) on port 3000...');
162
- const cf = (0, child_process_1.spawn)('npx', ['cloudflared', 'tunnel', '--url', 'http://localhost:3000'], { env, shell: true });
163
- children.push({
164
- kill: () => { try {
165
- process.kill(cf.pid, 'SIGTERM');
166
- }
167
- catch (e) { } },
168
- forceKill: () => { try {
169
- process.kill(cf.pid, 'SIGKILL');
170
- }
171
- catch (e) { } }
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
- // Optionally print errors if they are real errors, but cloudflared logs everything to stderr
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
- try {
205
- require('child_process').execSync('pkill -f ts-node');
206
- require('child_process').execSync('pkill -f uvicorn');
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: response.choices[0].message.content,
14
- reasoning_content: response.choices[0].message.reasoning_content || null,
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
- // Kirim riwayat percakapan ke Python ML Engine untuk diproses oleh LangChain
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
- const toolsRequiringReasoning = ['run_command', 'write_to_file', 'replace_file_content', 'multi_replace_file_content', 'execute_script'];
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
- const fastReturnTools = ['transfer_token', 'transfer_native', 'swap_token', 'bridge_token', 'mint_nft', 'custom_tx', 'revoke_approval', 'supply_aave', 'deposit_yield_vault', 'provide_liquidity_v3', 'send_telegram_file'];
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', 'send_telegram_file'
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(`_⚡ Running tool: ${toolName}..._`);
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
- if (!['transfer_token', 'transfer_native', 'swap_token', 'bridge_token', 'mint_nft', 'custom_tx', 'revoke_approval', 'supply_aave', 'deposit_yield_vault', 'provide_liquidity_v3', 'send_telegram_file'].includes(toolName)) {
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: await getSystemPrompt('os', input) },
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
- onChunk('[CLEAR_STREAM]');
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 jrny to idr", "2 eth in usd", "cek saldo gue dirupiahin"), you MUST pass that amount into the 'get_price_and_fiat_value' tool's 'amount' parameter.
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/rupiah. 'analyze_market' does not do fiat conversion.
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 {