nyxora 26.7.2 → 26.7.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.
Files changed (167) hide show
  1. package/CHANGELOG.md +95 -47
  2. package/README.md +23 -19
  3. package/bin/nyxora.mjs +4 -6
  4. package/dist/launcher.js +44 -8
  5. package/dist/packages/core/src/agent/bridgeWatcher.js +3 -2
  6. package/dist/packages/core/src/agent/cronManager.js +2 -2
  7. package/dist/packages/core/src/agent/llmProvider.js +242 -0
  8. package/dist/packages/core/src/agent/nyxDaemon.js +97 -0
  9. package/dist/packages/core/src/agent/osAgent.js +203 -29
  10. package/dist/packages/core/src/agent/reasoning.js +353 -60
  11. package/dist/packages/core/src/agent/reasoningScratchpad.js +47 -0
  12. package/dist/packages/core/src/agent/transactionManager.js +36 -31
  13. package/dist/packages/core/src/agent/web3Agent.js +263 -37
  14. package/dist/packages/core/src/cognitive/cognitiveManager.js +63 -10
  15. package/dist/packages/core/src/config/parser.js +10 -4
  16. package/dist/packages/core/src/gateway/chat.js +56 -13
  17. package/dist/packages/core/src/gateway/cli.js +3 -0
  18. package/dist/packages/core/src/gateway/discordAdapter.js +81 -0
  19. package/dist/packages/core/src/gateway/server.js +58 -9
  20. package/dist/packages/core/src/gateway/setup-ml.js +64 -0
  21. package/dist/packages/core/src/gateway/setup.js +39 -3
  22. package/dist/packages/core/src/gateway/telegram.js +120 -24
  23. package/dist/packages/core/src/memory/episodic.js +47 -3
  24. package/dist/packages/core/src/memory/logger.js +120 -2
  25. package/dist/packages/core/src/memory/promotionEngine.js +18 -5
  26. package/dist/packages/core/src/system/agentskills.js +10 -0
  27. package/dist/packages/core/src/system/plugins/GoogleWorkspacePlugin.js +1 -1
  28. package/dist/packages/core/src/system/plugins/SystemCorePlugin.js +1 -1
  29. package/dist/packages/core/src/system/plugins/SystemExternalPlugin.js +1 -1
  30. package/dist/packages/core/src/system/plugins/SystemPluginInstallerPlugin.js +1 -1
  31. package/dist/packages/core/src/system/plugins/SystemSocialPlugin.js +1 -1
  32. package/dist/packages/core/src/system/plugins/SystemWebPlugin.js +1 -1
  33. package/dist/packages/core/src/system/plugins/SystemWorkspacePlugin.js +3 -3
  34. package/dist/packages/core/src/system/plugins/createSkill.js +1 -1
  35. package/dist/packages/core/src/system/skillExtractor.js +16 -5
  36. package/dist/packages/core/src/system/skills/forgetMemory.js +6 -4
  37. package/dist/packages/core/src/system/skills/scheduleTask.js +7 -2
  38. package/dist/packages/core/src/test_mainnet.js +45 -0
  39. package/dist/packages/core/src/utils/contextSummarizer.js +82 -0
  40. package/dist/packages/core/src/utils/skillManager.js +1 -1
  41. package/dist/packages/core/src/utils/streamSimulator.js +85 -0
  42. package/dist/packages/core/src/web3/aggregator/providers/ArbitrumBridgeProvider.js +28 -11
  43. package/dist/packages/core/src/web3/aggregator/providers/OpBridgeProvider.js +41 -27
  44. package/dist/packages/core/src/web3/aggregator/providers/TestnetSwapProvider.js +65 -0
  45. package/dist/packages/core/src/web3/aggregator/routeSelector.js +7 -1
  46. package/dist/packages/core/src/web3/plugins/Web3DefiPlugin.js +1 -1
  47. package/dist/packages/core/src/web3/plugins/Web3MarketPlugin.js +5 -125
  48. package/dist/packages/core/src/web3/plugins/Web3SecurityPlugin.js +1 -1
  49. package/dist/packages/core/src/web3/plugins/Web3WalletPlugin.js +1 -1
  50. package/dist/packages/core/src/web3/skills/bridgeToken.js +19 -4
  51. package/dist/packages/core/src/web3/skills/checkAddress.js +2 -0
  52. package/dist/packages/core/src/web3/skills/checkPortfolio.js +22 -2
  53. package/dist/packages/core/src/web3/skills/checkSecurity.js +2 -0
  54. package/dist/packages/core/src/web3/skills/confirmPendingTx.js +1 -1
  55. package/dist/packages/core/src/web3/skills/createLimitOrder.js +15 -1
  56. package/dist/packages/core/src/web3/skills/customTx.js +3 -0
  57. package/dist/packages/core/src/web3/skills/defiLending.js +2 -0
  58. package/dist/packages/core/src/web3/skills/getBalance.js +2 -0
  59. package/dist/packages/core/src/web3/skills/getPrice.js +134 -27
  60. package/dist/packages/core/src/web3/skills/getTxHistory.js +2 -0
  61. package/dist/packages/core/src/web3/skills/marketAnalysis.js +28 -191
  62. package/dist/packages/core/src/web3/skills/mintNft.js +3 -0
  63. package/dist/packages/core/src/web3/skills/provideLiquidity.js +16 -12
  64. package/dist/packages/core/src/web3/skills/revokeApprovals.js +2 -0
  65. package/dist/packages/core/src/web3/skills/swapToken.js +20 -2
  66. package/dist/packages/core/src/web3/skills/transfer.js +2 -0
  67. package/dist/packages/core/src/web3/skills/yieldVault.js +2 -0
  68. package/dist/packages/core/src/web3/utils/chains.js +19 -0
  69. package/dist/packages/core/src/web3/utils/marketEngine.js +26 -33
  70. package/dist/packages/signer/src/NyxoraSigner.js +181 -0
  71. package/dist/packages/signer/src/index.js +17 -0
  72. package/dist/packages/signer/src/server.js +25 -161
  73. package/launcher.ts +38 -9
  74. package/package.json +6 -2
  75. package/packages/core/package.json +21 -10
  76. package/packages/core/src/agent/bridgeWatcher.ts +3 -2
  77. package/packages/core/src/agent/cronManager.ts +2 -2
  78. package/packages/core/src/agent/llmProvider.ts +219 -0
  79. package/packages/core/src/agent/nyxDaemon.ts +104 -0
  80. package/packages/core/src/agent/osAgent.ts +230 -32
  81. package/packages/core/src/agent/reasoning.ts +376 -64
  82. package/packages/core/src/agent/reasoningScratchpad.ts +45 -0
  83. package/packages/core/src/agent/transactionManager.ts +36 -28
  84. package/packages/core/src/agent/web3Agent.ts +291 -40
  85. package/packages/core/src/cognitive/cognitiveManager.ts +65 -10
  86. package/packages/core/src/cognitive/prompts/web3/market-analysis.md +24 -0
  87. package/packages/core/src/cognitive/prompts/web3/portfolio-review.md +29 -0
  88. package/packages/core/src/cognitive/prompts/web3/risk-assessment.md +28 -0
  89. package/packages/core/src/cognitive/prompts/web3/trade-planning.md +30 -0
  90. package/packages/core/src/config/parser.ts +15 -4
  91. package/packages/core/src/gateway/chat.ts +57 -15
  92. package/packages/core/src/gateway/cli.ts +4 -0
  93. package/packages/core/src/gateway/discordAdapter.ts +87 -0
  94. package/packages/core/src/gateway/server.ts +66 -10
  95. package/packages/core/src/gateway/setup-ml.ts +68 -0
  96. package/packages/core/src/gateway/setup.ts +41 -3
  97. package/packages/core/src/gateway/telegram.ts +138 -27
  98. package/packages/core/src/memory/episodic.ts +64 -3
  99. package/packages/core/src/memory/logger.ts +142 -2
  100. package/packages/core/src/memory/promotionEngine.ts +19 -5
  101. package/packages/core/src/system/agentskills.ts +10 -0
  102. package/packages/core/src/system/plugins/GoogleWorkspacePlugin.ts +1 -1
  103. package/packages/core/src/system/plugins/SystemCorePlugin.ts +1 -1
  104. package/packages/core/src/system/plugins/SystemExternalPlugin.ts +1 -1
  105. package/packages/core/src/system/plugins/SystemPluginInstallerPlugin.ts +1 -1
  106. package/packages/core/src/system/plugins/SystemSocialPlugin.ts +1 -1
  107. package/packages/core/src/system/plugins/SystemWebPlugin.ts +1 -1
  108. package/packages/core/src/system/plugins/SystemWorkspacePlugin.ts +3 -3
  109. package/packages/core/src/system/plugins/createSkill.ts +1 -1
  110. package/packages/core/src/system/skillExtractor.ts +16 -5
  111. package/packages/core/src/system/skills/forgetMemory.ts +7 -4
  112. package/packages/core/src/system/skills/scheduleTask.ts +7 -2
  113. package/packages/core/src/utils/contextSummarizer.ts +100 -0
  114. package/packages/core/src/utils/skillManager.ts +1 -1
  115. package/packages/core/src/utils/streamSimulator.ts +88 -0
  116. package/packages/core/src/web3/aggregator/providers/ArbitrumBridgeProvider.ts +30 -11
  117. package/packages/core/src/web3/aggregator/providers/OpBridgeProvider.ts +43 -29
  118. package/packages/core/src/web3/aggregator/routeSelector.ts +6 -1
  119. package/packages/core/src/web3/plugins/Web3DefiPlugin.ts +1 -1
  120. package/packages/core/src/web3/plugins/Web3MarketPlugin.ts +6 -125
  121. package/packages/core/src/web3/plugins/Web3SecurityPlugin.ts +1 -1
  122. package/packages/core/src/web3/plugins/Web3WalletPlugin.ts +1 -1
  123. package/packages/core/src/web3/skills/bridgeToken.ts +21 -7
  124. package/packages/core/src/web3/skills/checkAddress.ts +2 -0
  125. package/packages/core/src/web3/skills/checkPortfolio.ts +23 -2
  126. package/packages/core/src/web3/skills/checkSecurity.ts +2 -0
  127. package/packages/core/src/web3/skills/confirmPendingTx.ts +1 -1
  128. package/packages/core/src/web3/skills/createLimitOrder.ts +17 -2
  129. package/packages/core/src/web3/skills/customTx.ts +3 -0
  130. package/packages/core/src/web3/skills/defiLending.ts +2 -0
  131. package/packages/core/src/web3/skills/getBalance.ts +2 -0
  132. package/packages/core/src/web3/skills/getPrice.ts +134 -30
  133. package/packages/core/src/web3/skills/getTxHistory.ts +2 -0
  134. package/packages/core/src/web3/skills/marketAnalysis.ts +45 -188
  135. package/packages/core/src/web3/skills/mintNft.ts +3 -0
  136. package/packages/core/src/web3/skills/provideLiquidity.ts +16 -10
  137. package/packages/core/src/web3/skills/revokeApprovals.ts +2 -0
  138. package/packages/core/src/web3/skills/swapToken.ts +20 -3
  139. package/packages/core/src/web3/skills/transfer.ts +2 -0
  140. package/packages/core/src/web3/skills/yieldVault.ts +2 -0
  141. package/packages/core/src/web3/utils/chains.ts +12 -0
  142. package/packages/core/src/web3/utils/marketEngine.ts +27 -33
  143. package/packages/dashboard/dist/assets/index-BTfp141V.js +18 -0
  144. package/packages/dashboard/dist/assets/index-DK2sTU47.css +1 -0
  145. package/packages/dashboard/dist/index.html +2 -2
  146. package/packages/dashboard/package.json +10 -10
  147. package/packages/mcp-server/dist/server.js +5 -1
  148. package/packages/mcp-server/package.json +6 -6
  149. package/packages/mcp-server/src/server.ts +11 -7
  150. package/packages/policy/package.json +3 -3
  151. package/packages/signer/package.json +16 -6
  152. package/packages/signer/src/NyxoraSigner.ts +161 -0
  153. package/packages/signer/src/index.ts +1 -0
  154. package/packages/signer/src/server.ts +25 -135
  155. package/dist/packages/core/src/agent/honchoDaemon.js +0 -91
  156. package/dist/packages/core/src/cognitive/prompts/autonomous/binance-trading-integration.md +0 -54
  157. package/dist/packages/core/src/gateway/test.js +0 -15
  158. package/dist/packages/core/src/web3/Web3DefiPlugin.js +0 -28
  159. package/dist/packages/core/src/web3/aggregator/aggregatorMainnet.js +0 -260
  160. package/dist/packages/core/src/web3/aggregator/aggregatorTestnet.js +0 -119
  161. package/dist/packages/core/src/web3/skills/nativeOpBridge.js +0 -71
  162. package/packages/core/src/agent/honchoDaemon.ts +0 -96
  163. package/packages/core/src/cognitive/prompts/autonomous/binance-trading-integration.md +0 -41
  164. package/packages/core/src/plugin/registry.test.ts +0 -46
  165. package/packages/core/src/utils/formatter.test.ts +0 -41
  166. package/packages/dashboard/dist/assets/index-CSoNa5cx.css +0 -1
  167. package/packages/dashboard/dist/assets/index-DbRWEoSr.js +0 -16
@@ -7,7 +7,8 @@ import { loadConfig, loadApiKeys } from '../config/parser';
7
7
  import { Logger } from '../memory/logger';
8
8
  import { Tracker } from '../gateway/tracker';
9
9
  import { episodicDB } from '../memory/episodic';
10
-
10
+ import { createSmartStreamWrapper } from '../utils/streamSimulator';
11
+ import { needsCompression, compressHistory } from '../utils/contextSummarizer';
11
12
 
12
13
  import { getPath } from '../config/paths';
13
14
  import pc from 'picocolors';
@@ -17,9 +18,8 @@ export const logger = new Logger();
17
18
 
18
19
 
19
20
  import { getOpenAI, executeWithRetry } from '../utils/llmUtils';
20
-
21
-
22
- function getSystemPrompt(context: 'web3' | 'os' | 'general' = 'general'): string {
21
+ import { txManager } from './transactionManager';
22
+ async function getSystemPrompt(context: 'web3' | 'os' | 'general' = 'general', userInput: string = ''): Promise<string> {
23
23
  const config = loadConfig();
24
24
  const currentDateTime = new Date().toLocaleString('en-US');
25
25
  let basePrompt = "";
@@ -34,7 +34,7 @@ IMPORTANT: The <think> block is strictly for internal monologue. Your final answ
34
34
 
35
35
  [WEB3 EXECUTION WORKFLOW]
36
36
  CRITICAL RULE 1: NEVER expose internal JSON tool calls. Explain the outcome naturally.
37
- CRITICAL RULE 2: STRICT LANGUAGE MATCHING. Reply in the exact same language as the user's LATEST prompt.
37
+ CRITICAL RULE: STRICT LANGUAGE MATCHING. Reply in the exact same language as the user's LATEST prompt, UNLESS the Episodic Memories or Cognitive Skills specify a strict language preference.
38
38
  CRITICAL RULE 3: DEFAULT CHAIN HANDLING. Default to: ${config.agent.default_chain} unless specified.
39
39
  CRITICAL RULE 4: CONDITIONAL PARALLEL EXECUTION. Parallel tool execution is ONLY allowed if there are zero data dependencies.
40
40
  CRITICAL RULE 5: TRANSACTION EXECUTION. For ALL state-changing transactions (swap, bridge, transfer), execute IMMEDIATELY. It will trigger a secure popup.
@@ -51,7 +51,7 @@ IMPORTANT: The <think> block is strictly for internal monologue. Your final answ
51
51
 
52
52
  [OS EXECUTION WORKFLOW]
53
53
  CRITICAL RULE 1: NEVER expose internal JSON tool calls. Explain the outcome naturally.
54
- CRITICAL RULE 2: STRICT LANGUAGE MATCHING. Reply in the exact same language as the user's LATEST prompt.
54
+ CRITICAL RULE: STRICT LANGUAGE MATCHING. Reply in the exact same language as the user's LATEST prompt, UNLESS the Episodic Memories or Cognitive Skills specify a strict language preference.
55
55
  CRITICAL RULE 3: FILE SYSTEM SAFETY. You are STRICTLY FORBIDDEN from modifying config.yaml, rpc_key.yaml, or policy.yaml using terminal commands like sed or echo.
56
56
  CRITICAL RULE 4: CRON JOBS VS LIMIT ORDERS. Do NOT use schedule_task for price-based trading triggers. Use schedule_task for time-based recurring tasks.
57
57
  CRITICAL RULE 5: TOOL CONFIDENCE. NEVER fabricate file contents or command outputs.`;
@@ -63,7 +63,7 @@ CRITICAL: You MUST use a Chain of Thought approach for every response. Enclose y
63
63
  IMPORTANT: The <think> block is strictly for internal monologue. Your final answer must be OUTSIDE and AFTER the </think> tag.
64
64
 
65
65
  [GENERAL WORKFLOW]
66
- CRITICAL RULE 1: STRICT LANGUAGE MATCHING. Reply in the exact same language as the user's LATEST prompt.
66
+ CRITICAL RULE 1: STRICT LANGUAGE MATCHING. Reply in the exact same language as the user's LATEST prompt, UNLESS the Episodic Memories or Cognitive Skills specify a strict language preference.
67
67
  CRITICAL RULE 2: BE HELPFUL AND CONCISE. You do not have Web3 or OS tools in this context. If the user asks for Web3 or OS tasks, politely inform them to rephrase using clear keywords like 'transfer', 'harga', 'file', 'email', etc.`;
68
68
  }
69
69
 
@@ -129,51 +129,187 @@ Do NOT perform any web3 tasks or generic answers until they provide all 4 detail
129
129
  console.error('Failed to read policy.yaml:', error);
130
130
  }
131
131
 
132
- // Inject Episodic Memories (Smart Suggestions Context)
132
+ // Inject Episodic Memories via Python RAG
133
133
  try {
134
- const recentMemories = episodicDB.getMemories().slice(0, 10);
135
- if (recentMemories.length > 0) {
136
- basePrompt += `\n\n--- EPISODIC MEMORIES (SMART SUGGESTIONS) ---\nUse these recent observations to proactively suggest or autocomplete parameters (like networks or tokens) without asking the user if they align with the current request:\n`;
137
- recentMemories.forEach(mem => {
138
- basePrompt += `- [${mem.category.toUpperCase()}] ${mem.fact} (Confidence: ${(mem.confidence * 100).toFixed(0)}%)\n`;
139
- });
134
+ const ragRes = await fetch('http://localhost:8000/memory/rag', {
135
+ method: 'POST',
136
+ headers: { 'Content-Type': 'application/json' },
137
+ body: JSON.stringify({ query: userInput, top_k: 5 })
138
+ });
139
+ if (ragRes.ok) {
140
+ const ragData = await ragRes.json();
141
+ if (ragData.memories && ragData.memories.length > 0) {
142
+ basePrompt += `\n\n--- EPISODIC MEMORIES (SMART SUGGESTIONS) ---\n`;
143
+ ragData.memories.forEach((mem: string) => {
144
+ basePrompt += `- ${mem}\n`;
145
+ });
146
+ }
140
147
  }
141
- } catch {}
148
+ } catch (e) {
149
+ // Fallback or ignore if Python ML engine is down
150
+ }
142
151
 
143
152
  // V3: Inject Personalized Risk Profile
144
153
  try {
145
154
  const profile = logger.getUserProfile();
146
- const personas = episodicDB.getPersonas();
147
155
 
148
- if (profile || personas.length > 0) {
156
+ if (profile) {
149
157
  basePrompt += `\n\n--- [USER_PERSONA] RISK PROFILE & PREFERENCES ---\n`;
150
- if (profile) {
151
- basePrompt += `Risk Level: ${profile.risk_level}\n`;
152
- basePrompt += `Max Slippage Tolerance: ${profile.max_slippage}%\n`;
153
- basePrompt += `Avoid Memecoins: ${profile.avoid_memecoins ? 'YES' : 'NO'}\n`;
154
- if (profile.custom_rules) {
155
- basePrompt += `Custom Rules: ${profile.custom_rules}\n`;
156
- }
157
- basePrompt += `CRITICAL: You MUST adhere to these risk parameters when advising the user or executing tools. If a requested action violates these parameters (e.g., buying a high-risk memecoin when 'Avoid Memecoins' is YES), you MUST warn the user and refuse execution unless they explicitly override.\n`;
158
+ basePrompt += `Risk Level: ${profile.risk_level}\n`;
159
+ basePrompt += `Max Slippage Tolerance: ${profile.max_slippage}%\n`;
160
+ basePrompt += `Avoid Memecoins: ${profile.avoid_memecoins ? 'YES' : 'NO'}\n`;
161
+ if (profile.custom_rules) {
162
+ basePrompt += `Custom Rules: ${profile.custom_rules}\n`;
158
163
  }
164
+ basePrompt += `CRITICAL: You MUST adhere to these risk parameters when advising the user or executing tools. If a requested action violates these parameters (e.g., buying a high-risk memecoin when 'Avoid Memecoins' is YES), you MUST warn the user and refuse execution unless they explicitly override.\n`;
165
+ }
166
+ } catch {}
167
+
168
+ // HIGHEST PRIORITY: Inject observed user communication style.
169
+ // This section is placed LAST so it overrides any default tone rules above (including IDENTITY.md).
170
+ try {
171
+ // Only use traits with confidence >= 0.5 (confirmed by multiple audit cycles)
172
+ const strongPersonas = episodicDB.getStrongPersonas(0.5);
173
+
174
+ if (strongPersonas.length > 0) {
175
+ basePrompt += `\n\n--- ⚡ OVERRIDE: USER COMMUNICATION STYLE (HIGHEST PRIORITY — OVERRIDES ALL RULES ABOVE) ---\n`;
176
+ basePrompt += `CRITICAL: These are CONFIRMED user preferences observed across multiple conversations. You MUST apply them IMMEDIATELY and CONSISTENTLY, overriding any default tone or style instructions above.\n\n`;
159
177
 
160
- if (personas.length > 0) {
161
- basePrompt += `\nObserved Traits (Dialectic Modeling):\n`;
162
- personas.forEach(p => {
163
- basePrompt += `- ${p.trait} (Confidence: ${(p.confidence * 100).toFixed(0)}%)\n`;
164
- });
165
- basePrompt += `Adapt your tone, vocabulary, and suggestions to align with these observed user traits.\n`;
166
- }
178
+ strongPersonas.forEach(p => {
179
+ const label = p.category ? `[${p.category.toUpperCase()}]` : '[PREFERENCE]';
180
+ basePrompt += `${label} ${p.trait}\n`;
181
+ });
182
+
183
+ basePrompt += `\nMANDATORY ENFORCEMENT:\n`;
184
+ basePrompt += `- You MUST adapt your tone, language, and style to match the detected user preferences above.\n`;
185
+ basePrompt += `- If the user communicates informally or casually → respond casually. Do NOT be formal or robotic.\n`;
186
+ basePrompt += `- If the user communicates in a specific language or dialect → respond in that same language/style.\n`;
187
+ basePrompt += `- If the user uses slang or informal words → mirror that vocabulary naturally.\n`;
188
+ basePrompt += `- This is a HARD RULE. It overrides any default tone in IDENTITY.md or other instructions above.\n`;
167
189
  }
168
190
  } catch {}
169
191
 
170
192
  return basePrompt;
171
193
  }
172
194
 
195
+ // ──────────────────────────────────────────────────────────────────
196
+ // Module-level routing keyword constants
197
+ // Defined here once and shared by both processUserInput() and processUserInputStream()
198
+ // to ensure consistent routing across sync and streaming paths.
199
+ //
200
+ // NOTE: Arrays are intentionally multilingual. Nyxora targets a global user base.
201
+ // Indonesian terms are included as the primary non-English locale.
202
+ // To add another language, extend these arrays — no other changes required.
203
+ // ──────────────────────────────────────────────────────────────────
204
+ const OS_KEYWORDS: string[] = [
205
+ // Files & Documents
206
+ 'excel', 'xlsx', 'spreadsheet', 'generate excel',
207
+ 'file', 'folder', 'directory', 'read file', 'write file', 'pdf', 'word', 'docx', 'document',
208
+ // Terminal & Git
209
+ 'terminal', 'command', 'shell', 'bash', 'script', 'run command',
210
+ 'git', 'commit', 'push', 'pull', 'clone', 'branch', 'merge',
211
+ // Web & Search
212
+ 'search web', 'google', 'browse', 'scrape', 'weather', 'news',
213
+ // Email & Workspace
214
+ 'email', 'gmail', 'google docs', 'google sheets', 'notion', 'calendar',
215
+ // Social & Media
216
+ 'twitter', 'tweet', 'x post', 'transcribe', 'audio',
217
+ // AI Settings
218
+ 'rename agent', 'change persona', 'update profile', 'update identity', 'setting',
219
+ // Summarization
220
+ 'summarize',
221
+ ];
222
+
223
+ const WEB3_KEYWORDS: string[] = [
224
+ // Transactions
225
+ 'swap', 'bridge', 'transfer', 'send', 'buy', 'sell',
226
+ 'mint', 'stake', 'unstake', 'claim', 'deposit', 'withdraw', 'approve',
227
+ // Assets & Wallets
228
+ 'token', 'crypto', 'coin', 'nft', 'wallet', 'address',
229
+ 'eth', 'bnb', 'usdt', 'usdc', 'sol', 'matic', 'arb', 'op', 'base',
230
+ // DeFi & Market
231
+ 'defi', 'dex', 'liquidity', 'pool', 'aave', 'uniswap', 'apy', 'apr',
232
+ 'price', 'chart', 'market', 'portfolio', 'balance',
233
+ 'gas', 'fee', 'slippage', 'transaction', 'tx',
234
+ // Chains
235
+ 'ethereum', 'polygon', 'arbitrum', 'optimism', 'bsc', 'mainnet', 'testnet',
236
+ 'on-chain', 'blockchain',
237
+ // Fiat & Currency
238
+ 'usd', 'eur', 'gbp', 'jpy', 'aud', 'idr', 'fiat', 'currency', 'convert', 'exchange', 'rate', 'value',
239
+ ];
240
+
241
+
242
+
243
+ // ── P2: Multi-Intent Decomposer ─────────────────────────────────────────────
244
+ /**
245
+ * Detects if the user message contains BOTH web3 AND os intents.
246
+ * Returns 'compound' when both are present so we can route to both agents.
247
+ */
248
+ function detectCompoundIntent(
249
+ lowerInput: string,
250
+ osKeywords: string[],
251
+ web3Keywords: string[]
252
+ ): { hasOs: boolean; hasWeb3: boolean } {
253
+ return {
254
+ hasOs: osKeywords.some(kw => lowerInput.includes(kw)),
255
+ hasWeb3: web3Keywords.some(kw => lowerInput.includes(kw)),
256
+ };
257
+ }
173
258
 
259
+ // ── P7: Task Planner ─────────────────────────────────────────────────────────
260
+ // Trigger keywords for complex requests that benefit from a planning step first.
261
+ // Intentionally multilingual: includes common terms from Indonesian (id), English (en),
262
+ // and universal finance/trading vocabulary to support Nyxora's global user base.
263
+ const PLAN_TRIGGER_KEYWORDS = [
264
+ // Indonesian
265
+ 'buatkan', 'buat rencana', 'rencanakan', 'strategi', 'gimana cara', 'bagaimana cara',
266
+ 'langkah', 'optimasi', 'apa yang harus',
267
+ // English
268
+ 'strategy', 'plan', 'planning', 'step by step', 'breakdown', 'optimize',
269
+ 'rebalance', 'what should i do', 'help me decide', 'how do i',
270
+ // Universal finance terms
271
+ 'roadmap', 'approach', 'framework',
272
+ ];
273
+
274
+ function shouldPlan(input: string): boolean {
275
+ const lower = input.toLowerCase();
276
+ return PLAN_TRIGGER_KEYWORDS.some(kw => lower.includes(kw));
277
+ }
278
+
279
+ async function runTaskPlanner(input: string, context: string): Promise<string> {
280
+ const config = loadConfig();
281
+ try {
282
+ const planRes = await executeWithRetry(async (client) =>
283
+ client.chat({
284
+ model: config.llm.model,
285
+ temperature: 0.2,
286
+ messages: [
287
+ {
288
+ role: 'system',
289
+ content: `You are a task planning assistant for a crypto AI agent.
290
+ The user has a complex request that needs structured execution.
291
+ Break it down into a clear, ordered execution plan with 3-6 concrete steps.
292
+ Each step should map to a specific action or tool call.
293
+ Be concise. Use bullet points. No fluff.
294
+ Context domain: ${context}`
295
+ },
296
+ { role: 'user', content: `Create an execution plan for: ${input}` }
297
+ ]
298
+ })
299
+ );
300
+ const plan = planRes.message?.content?.trim() || '';
301
+ if (!plan) return '';
302
+ console.log(pc.blue('[TaskPlanner] Plan generated, injecting into agent context.'));
303
+ return `\n\n--- 📋 TASK EXECUTION PLAN (follow this order) ---\n${plan}\n--- END PLAN ---\n`;
304
+ } catch {
305
+ return ''; // planning is best-effort, never block the agent
306
+ }
307
+ }
174
308
 
175
309
  import { processWeb3Intent } from './web3Agent';
176
310
  import { processOsIntent } from './osAgent';
311
+ import { processWeb3IntentStream } from './web3Agent';
312
+ import { processOsIntentStream } from './osAgent';
177
313
 
178
314
  export async function processUserInput(input: string, role: 'user' | 'system' = 'user', onProgress?: (msg: string) => void, sessionId?: string): Promise<string> {
179
315
  const lowerInput = input.toLowerCase();
@@ -186,48 +322,94 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
186
322
  .filter(m => (m.role === 'user' || m.role === 'assistant') && m.content)
187
323
  .map(m => ({ role: m.role === 'system' ? 'user' : m.role, content: m.content || "" }));
188
324
 
189
- const routerPrompt = `You are Nyxora's Semantic Intent Router. Your job is to classify the user's FINAL message into one of three categories: 'web3', 'os', or 'general'.
325
+ // Uses module-level OS_KEYWORDS and WEB3_KEYWORDS constants defined above.
326
+ // Routing logic: deterministic keyword check first (no LLM call needed),
327
+ // with LLM router as fallback for ambiguous inputs.
328
+
329
+
330
+ let context: 'web3' | 'os' | 'general' = 'general';
331
+ let preCheckMatched = false;
332
+
333
+ const compound = detectCompoundIntent(lowerInput, OS_KEYWORDS, WEB3_KEYWORDS);
334
+
335
+ // P2: Handle compound web3+os intent — run both agents and merge results
336
+ if (compound.hasWeb3 && compound.hasOs) {
337
+ console.log(pc.cyan('[Orchestrator] Compound intent detected (web3 + os). Running both agents sequentially.'));
338
+ logger.addEntry({ role, content: input }, sessionId);
339
+ const [web3Result, osResult] = await Promise.allSettled([
340
+ processWeb3Intent(input, role, onProgress, sessionId),
341
+ processOsIntent(input, role, onProgress, sessionId),
342
+ ]);
343
+ const parts: string[] = [];
344
+ if (web3Result.status === 'fulfilled' && web3Result.value) parts.push(web3Result.value);
345
+ if (osResult.status === 'fulfilled' && osResult.value) parts.push(osResult.value);
346
+ return parts.join('\n\n---\n\n') || '⚠️ Both agents returned empty responses.';
347
+ }
348
+
349
+ // Single-intent routing
350
+ if (compound.hasOs) {
351
+ context = 'os';
352
+ preCheckMatched = true;
353
+ } else if (compound.hasWeb3) {
354
+ context = 'web3';
355
+ preCheckMatched = true;
356
+ }
357
+
358
+ if (preCheckMatched) {
359
+ console.log(pc.cyan(`[Orchestrator] Intent pre-classified (keyword match) as: ${context.toUpperCase()}`));
360
+ } else {
361
+ // ── Fallback: LLM Router (untuk intent ambigu / percakapan umum) ─────────
362
+ const routerPrompt = `You are Nyxora's Semantic Intent Router. Your job is to classify the user's FINAL message into one of three categories: 'web3', 'os', or 'general'.
190
363
  Rules:
191
364
  1. FOCUS ONLY ON THE FINAL MESSAGE. History is only for context.
192
- 2. The user may speak in ANY language, including casual slang, idioms, or abbreviations (e.g., 'tf', 'wd', 'buy', 'sell'). Translate their core intent logically.
193
- 3. If the core intent involves blockchain, crypto, bridging, swapping, trading, sending/receiving, tokens, wallets, or transactions, reply 'web3'.
194
- 4. If the core intent involves OS automation, web search, weather, emails, files, terminal, or changing AI settings, reply 'os'.
365
+ 2. The user may speak in ANY language, including casual slang, idioms, or abbreviations.
366
+ 3. If the core intent involves blockchain, crypto, bridging, swapping, trading, sending/receiving, tokens, wallets, transactions, OR asking for the price/conversion of ANY asset to fiat, reply 'web3'.
367
+ 4. If the core intent involves OS automation, weather, emails, files, terminal, changing AI settings, OR asking ANY question that requires a web search or real-world factual lookup (e.g., 'who won the game', 'what is the registration date', 'cek info', 'cari tahu'), reply 'os'.
195
368
  5. If it is purely casual conversation, chit-chat, or greetings, reply 'general'.
196
369
  Reply with EXACTLY ONE WORD: web3, os, or general.`;
197
370
 
198
- const routerMessages = [
199
- { role: 'system', content: routerPrompt },
200
- ...textOnlyHistory.slice(-10),
201
- { role: 'user', content: input }
202
- ];
371
+ const routerMessages = [
372
+ { role: 'system', content: routerPrompt },
373
+ ...textOnlyHistory.slice(-10),
374
+ { role: 'user', content: input }
375
+ ];
203
376
 
204
- let context: 'web3' | 'os' | 'general' = 'general';
205
-
206
- try {
207
- const routerResponse = await executeWithRetry(async (client) => {
208
- return await client.chat({
209
- model: config.llm.model,
210
- messages: routerMessages as any,
211
- temperature: 0.1,
212
- max_tokens: 1000
213
- });
214
- }, 3); // 3 retries for transient 503/429 errors
215
-
216
- let contextResponse = (routerResponse.message.content || 'general').toLowerCase().trim();
217
-
218
- if (contextResponse.includes('web3')) context = 'web3';
219
- else if (contextResponse.includes('os')) context = 'os';
220
- else context = 'general';
221
- } catch (e) {
222
- console.warn(`[Orchestrator] Router LLM failed, falling back to general. Error:`, e);
223
- context = 'general';
377
+ try {
378
+ const routerResponse = await executeWithRetry(async (client) => {
379
+ return await client.chat({
380
+ model: config.llm.model,
381
+ messages: routerMessages as any,
382
+ temperature: 0.1,
383
+ max_tokens: 1000
384
+ });
385
+ }, 3); // 3 retries for transient 503/429 errors
386
+
387
+ let contextResponse = (routerResponse.message.content || 'general').toLowerCase().trim();
388
+
389
+ if (contextResponse.includes('web3')) context = 'web3';
390
+ else if (contextResponse.includes('os')) context = 'os';
391
+ else context = 'general';
392
+ } catch (e) {
393
+ console.warn(`[Orchestrator] Router LLM failed, falling back to general. Error:`, e);
394
+ context = 'general';
395
+ }
396
+
397
+ console.log(pc.magenta(`[Orchestrator] Intent classified as: ${context.toUpperCase()}`));
224
398
  }
225
399
 
226
- console.log(pc.magenta(`[Orchestrator] Intent classified as: ${context.toUpperCase()}`));
227
-
228
400
  if (context === 'web3') {
401
+ // P7: Inject task plan for complex requests
402
+ const planInjection = shouldPlan(input) ? await runTaskPlanner(input, 'web3') : '';
403
+ if (planInjection) {
404
+ // Prepend plan as a system note in the input so agent sees it
405
+ return await processWeb3Intent(planInjection + '\n\nUSER REQUEST: ' + input, role, onProgress, sessionId);
406
+ }
229
407
  return await processWeb3Intent(input, role, onProgress, sessionId);
230
408
  } else if (context === 'os') {
409
+ const planInjection = shouldPlan(input) ? await runTaskPlanner(input, 'os') : '';
410
+ if (planInjection) {
411
+ return await processOsIntent(planInjection + '\n\nUSER REQUEST: ' + input, role, onProgress, sessionId);
412
+ }
231
413
  return await processOsIntent(input, role, onProgress, sessionId);
232
414
  } else {
233
415
  // General Agent: Use osAgent logic but without execution tools to save tokens.
@@ -236,7 +418,7 @@ Reply with EXACTLY ONE WORD: web3, os, or general.`;
236
418
  logger.addEntry({ role, content: input }, sessionId);
237
419
 
238
420
  const messages = [
239
- { role: 'system', content: getSystemPrompt('general') },
421
+ { role: 'system', content: await getSystemPrompt('general', input) },
240
422
  ...textOnlyHistory,
241
423
  { role: 'user', content: input }
242
424
  ];
@@ -278,3 +460,133 @@ Reply with EXACTLY ONE WORD: web3, os, or general.`;
278
460
  }
279
461
  }
280
462
  }
463
+
464
+ /**
465
+ * Streaming variant of processUserInput().
466
+ * Calls onChunk() for each LLM token as it arrives.
467
+ * Falls back to onChunk() with full response if streaming is not supported.
468
+ */
469
+ export async function processUserInputStream(
470
+ input: string,
471
+ originalOnChunk: (text: string) => void,
472
+ onProgress?: (msg: string) => void,
473
+ sessionId?: string
474
+ ): Promise<string> {
475
+ const smartStream = createSmartStreamWrapper(originalOnChunk);
476
+ const onChunk = smartStream.onChunk;
477
+
478
+ try {
479
+ const lowerInput = input.toLowerCase();
480
+ const config = loadConfig();
481
+ const history = logger.getHistory(sessionId);
482
+
483
+ const textOnlyHistory = history
484
+ .filter(m => (m.role === 'user' || m.role === 'assistant') && m.content)
485
+ .map(m => ({ role: m.role === 'system' ? 'user' : m.role, content: m.content || '' }));
486
+
487
+ // Use module-level keyword constants for consistent routing across sync and stream paths.
488
+ // detectCompoundIntent and shouldPlan are also shared from the module scope.
489
+
490
+ let context: 'web3' | 'os' | 'general' = 'general';
491
+ let preCheckMatched = false;
492
+
493
+ // P2: Compound intent detection (stream path)
494
+ const streamCompound = detectCompoundIntent(lowerInput, OS_KEYWORDS, WEB3_KEYWORDS);
495
+
496
+ const pendingTxs = txManager.getPending();
497
+ if (pendingTxs.length > 0) {
498
+ context = 'web3';
499
+ preCheckMatched = true;
500
+ } else if (streamCompound.hasWeb3 && streamCompound.hasOs) {
501
+ // Compound: stream both agents and concatenate
502
+ console.log(pc.cyan('[Stream Orchestrator] Compound intent detected (web3 + os).'));
503
+ logger.addEntry({ role: 'user', content: input }, sessionId);
504
+ const [web3R, osR] = await Promise.allSettled([
505
+ processWeb3IntentStream(input, onChunk, onProgress, sessionId),
506
+ processOsIntentStream(input, onChunk, onProgress, sessionId),
507
+ ]);
508
+ const parts: string[] = [];
509
+ if (web3R.status === 'fulfilled' && web3R.value) parts.push(web3R.value);
510
+ if (osR.status === 'fulfilled' && osR.value) parts.push(osR.value);
511
+ return parts.join('\n\n---\n\n') || '⚠️ Both agents returned empty responses.';
512
+ } else if (streamCompound.hasOs) {
513
+ context = 'os';
514
+ preCheckMatched = true;
515
+ } else if (streamCompound.hasWeb3) {
516
+ context = 'web3';
517
+ preCheckMatched = true;
518
+ }
519
+
520
+ if (!preCheckMatched) {
521
+ const routerPrompt = `You are Nyxora's Semantic Intent Router. Your job is to classify the user's FINAL message into one of three categories: 'web3', 'os', or 'general'.
522
+ Rules:
523
+ 1. FOCUS ONLY ON THE FINAL MESSAGE. History is only for context.
524
+ 2. The user may speak in ANY language, including casual slang, idioms, or abbreviations.
525
+ 3. If the core intent involves blockchain, crypto, bridging, swapping, trading, sending/receiving, tokens, wallets, transactions, OR asking for the price/conversion of ANY asset to fiat, reply 'web3'.
526
+ 4. If the core intent involves OS automation, weather, emails, files, terminal, changing AI settings, OR asking ANY question that requires a web search or real-world factual lookup (e.g., 'who won the game', 'what is the registration date', 'cek info', 'cari tahu'), reply 'os'.
527
+ 5. If it is purely casual conversation, chit-chat, or greetings, reply 'general'.
528
+ Reply with EXACTLY ONE WORD: web3, os, or general.`;
529
+ const routerMessages = [
530
+ { role: 'system', content: routerPrompt },
531
+ ...textOnlyHistory.slice(-10),
532
+ { role: 'user', content: input }
533
+ ];
534
+ try {
535
+ const routerResponse = await executeWithRetry(async (client) =>
536
+ client.chat({ model: config.llm.model, messages: routerMessages as any, temperature: 0.1, max_tokens: 1000 })
537
+ , 3);
538
+ const cr = (routerResponse.message.content || 'general').toLowerCase().trim();
539
+ if (cr.includes('web3')) context = 'web3';
540
+ else if (cr.includes('os')) context = 'os';
541
+ else context = 'general';
542
+ } catch {
543
+ context = 'general';
544
+ }
545
+ }
546
+
547
+ console.log(pc.cyan(`[Stream Orchestrator] Intent classified as: ${context.toUpperCase()}`));
548
+
549
+ let finalResult = '';
550
+ if (context === 'web3') {
551
+ // P7: Inject task plan for complex stream requests
552
+ const planInjection = shouldPlan(input) ? await runTaskPlanner(input, 'web3') : '';
553
+ const streamInput = planInjection ? planInjection + '\n\nUSER REQUEST: ' + input : input;
554
+ finalResult = await processWeb3IntentStream(streamInput, onChunk, onProgress, sessionId);
555
+ } else if (context === 'os') {
556
+ const planInjection = shouldPlan(input) ? await runTaskPlanner(input, 'os') : '';
557
+ const streamInput = planInjection ? planInjection + '\n\nUSER REQUEST: ' + input : input;
558
+ finalResult = await processOsIntentStream(streamInput, onChunk, onProgress, sessionId);
559
+ } else {
560
+ logger.addEntry({ role: 'user', content: input }, sessionId);
561
+ const messages = [
562
+ { role: 'system', content: await getSystemPrompt('general', input) },
563
+ ...textOnlyHistory,
564
+ { role: 'user', content: input }
565
+ ];
566
+ try {
567
+ let streamedContent = '';
568
+ const response = await executeWithRetry(async (client) =>
569
+ client.stream(
570
+ { model: config.llm.model, messages: messages as any },
571
+ (chunk: string) => { streamedContent += chunk; onChunk(chunk); }
572
+ )
573
+ );
574
+ let finalContent = response.message?.content || streamedContent || '';
575
+ finalContent = finalContent
576
+ .replace(/<(think|thought)[\s\S]*?<\/\1>\n?/gi, '')
577
+ .trim();
578
+ if (!finalContent) finalContent = '⚠️ The LLM returned an empty response. Please try again.';
579
+ logger.addEntry({ role: 'assistant', content: finalContent }, sessionId);
580
+ finalResult = finalContent;
581
+ } catch (error: any) {
582
+ const errorMsg = '⚠️ The system is experiencing LLM API rate limits. Please try again.';
583
+ logger.addEntry({ role: 'assistant', content: errorMsg }, sessionId);
584
+ finalResult = errorMsg;
585
+ }
586
+ }
587
+
588
+ return finalResult;
589
+ } finally {
590
+ await smartStream.wait();
591
+ }
592
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * ReasoningScratchpad — P1: Persistent Reasoning Memory
3
+ *
4
+ * Captures <think> blocks from LLM responses and makes the distilled
5
+ * reasoning available as context for subsequent turns within the same request.
6
+ * This mirrors how Claude/Gemini maintain internal chain-of-thought continuity.
7
+ */
8
+ export class ReasoningScratchpad {
9
+ private entries: { turn: number; summary: string }[] = [];
10
+
11
+ /**
12
+ * Extract and store the think block from a raw LLM response.
13
+ * Returns the cleaned response (think tags removed) for display.
14
+ */
15
+ public capture(rawContent: string, turn: number): string {
16
+ const thinkRegex = /<(think|thought|thinking|reasoning|analysis|reflection)>([\s\S]*?)<\/\1>/gi;
17
+ let match;
18
+ while ((match = thinkRegex.exec(rawContent)) !== null) {
19
+ const thinkText = match[2].trim();
20
+ if (thinkText.length > 20) {
21
+ // Distil to first 600 chars to avoid bloating the next prompt
22
+ this.entries.push({ turn, summary: thinkText.slice(0, 600) });
23
+ }
24
+ }
25
+ // Return content with think blocks stripped
26
+ return rawContent
27
+ .replace(/<(think|thought|thinking|reasoning|analysis|reflection)>[\s\S]*?<\/\1>\n?/gi, '')
28
+ .trim();
29
+ }
30
+
31
+ /**
32
+ * Build an injection string for the next turn's system prompt.
33
+ * Only surfaces the last 2 think blocks to keep token usage lean.
34
+ */
35
+ public getInjection(): string {
36
+ if (this.entries.length === 0) return '';
37
+ const recent = this.entries.slice(-2);
38
+ const lines = recent.map(e => `[Turn ${e.turn}] ${e.summary}`).join('\n');
39
+ return `\n\n--- 🧠 REASONING CONTINUITY (your prior internal thoughts) ---\n${lines}\nUse these prior conclusions to avoid re-deriving the same logic.\n`;
40
+ }
41
+
42
+ public isEmpty(): boolean {
43
+ return this.entries.length === 0;
44
+ }
45
+ }