nyxora 26.7.3 → 26.7.4

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 (55) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/README.md +2 -2
  3. package/dist/packages/core/src/agent/llmProvider.js +4 -1
  4. package/dist/packages/core/src/agent/nyxDaemon.js +1 -1
  5. package/dist/packages/core/src/agent/osAgent.js +149 -1
  6. package/dist/packages/core/src/agent/reasoning.js +1 -1
  7. package/dist/packages/core/src/agent/web3Agent.js +1 -1
  8. package/dist/packages/core/src/gateway/googleAuthModule.js +2 -0
  9. package/dist/packages/core/src/gateway/telegram.js +7 -7
  10. package/dist/packages/core/src/memory/episodic.js +16 -5
  11. package/dist/packages/core/src/system/plugins/GoogleWorkspacePlugin.js +9 -1
  12. package/dist/packages/core/src/system/skills/executeShell.js +31 -4
  13. package/dist/packages/core/src/system/skills/googleWorkspace.js +106 -1
  14. package/dist/packages/core/src/system/skills/searchWeb.js +13 -6
  15. package/dist/packages/core/src/web3/skills/getPrice.js +1 -1
  16. package/dist/packages/core/src/web3/skills/marketAnalysis.js +1 -1
  17. package/package.json +3 -2
  18. package/packages/core/package.json +1 -1
  19. package/packages/core/src/agent/llmProvider.ts +4 -2
  20. package/packages/core/src/agent/nyxDaemon.ts +1 -1
  21. package/packages/core/src/agent/osAgent.ts +152 -1
  22. package/packages/core/src/agent/reasoning.ts +1 -1
  23. package/packages/core/src/agent/web3Agent.ts +1 -1
  24. package/packages/core/src/gateway/googleAuthModule.ts +2 -0
  25. package/packages/core/src/gateway/telegram.ts +7 -7
  26. package/packages/core/src/memory/episodic.ts +21 -9
  27. package/packages/core/src/system/plugins/GoogleWorkspacePlugin.ts +14 -2
  28. package/packages/core/src/system/skills/executeShell.ts +38 -7
  29. package/packages/core/src/system/skills/googleWorkspace.ts +109 -0
  30. package/packages/core/src/system/skills/searchWeb.ts +12 -6
  31. package/packages/core/src/web3/skills/getPrice.ts +1 -1
  32. package/packages/core/src/web3/skills/marketAnalysis.ts +1 -1
  33. package/packages/dashboard/dist/assets/{index-BTfp141V.js → index-Czxksiao.js} +1 -1
  34. package/packages/dashboard/dist/index.html +1 -1
  35. package/packages/dashboard/package.json +1 -1
  36. package/packages/mcp-server/package.json +1 -1
  37. package/packages/ml-engine/__pycache__/config.cpython-313.pyc +0 -0
  38. package/packages/ml-engine/__pycache__/main.cpython-313.pyc +0 -0
  39. package/packages/ml-engine/config.py +59 -0
  40. package/packages/ml-engine/main.py +34 -0
  41. package/packages/ml-engine/requirements.txt +22 -0
  42. package/packages/ml-engine/routers/__init__.py +1 -0
  43. package/packages/ml-engine/routers/__pycache__/__init__.cpython-313.pyc +0 -0
  44. package/packages/ml-engine/routers/__pycache__/cognitive.cpython-313.pyc +0 -0
  45. package/packages/ml-engine/routers/__pycache__/critic.cpython-313.pyc +0 -0
  46. package/packages/ml-engine/routers/__pycache__/llm.cpython-313.pyc +0 -0
  47. package/packages/ml-engine/routers/__pycache__/market.cpython-313.pyc +0 -0
  48. package/packages/ml-engine/routers/__pycache__/memory.cpython-313.pyc +0 -0
  49. package/packages/ml-engine/routers/cognitive.py +98 -0
  50. package/packages/ml-engine/routers/critic.py +111 -0
  51. package/packages/ml-engine/routers/llm.py +38 -0
  52. package/packages/ml-engine/routers/market.py +332 -0
  53. package/packages/ml-engine/routers/memory.py +125 -0
  54. package/packages/policy/package.json +1 -1
  55. package/packages/signer/package.json +1 -1
@@ -139,7 +139,7 @@ async function searchWeb(query, depth = 1) {
139
139
  const config = (0, parser_1.loadConfig)();
140
140
  const provider = config.web_search?.provider || 'mesh';
141
141
  const vaultKeys = await (0, parser_1.loadApiKeys)();
142
- const creds = Object.keys(vaultKeys).length > 0 ? vaultKeys : (config.credentials || {});
142
+ const creds = { ...(config.credentials || {}), ...vaultKeys };
143
143
  let results = [];
144
144
  try {
145
145
  if (provider === 'tavily' && creds.tavily_key) {
@@ -228,11 +228,18 @@ async function searchWeb(query, depth = 1) {
228
228
  }
229
229
  }
230
230
  else {
231
- results = await searchSearxng(finalQuery, depth);
231
+ // Default 'mesh' provider - Prioritize DuckDuckGo as it's more reliable than public SearXNG instances
232
+ try {
233
+ results = await searchDuckDuckGo(finalQuery, depth);
234
+ }
235
+ catch (e) {
236
+ console.warn('[WebSearch] Mesh: DuckDuckGo failed. Falling back to SearXNG...');
237
+ results = await searchSearxng(finalQuery, depth);
238
+ }
232
239
  }
233
240
  }
234
241
  catch (e) {
235
- return `Failed to search the web: ${e.message}`;
242
+ return `[Search Failed] The web search failed due to an error: ${e.message}. CRITICAL INSTRUCTION: You MUST inform the user that the web search failed. Do NOT hallucinate or guess the answer.`;
236
243
  }
237
244
  if (results.length > 0) {
238
245
  searchCache.set(cacheKey, { data: results, timestamp: Date.now() });
@@ -260,17 +267,17 @@ exports.searchWebToolDefinition = {
260
267
  type: "function",
261
268
  function: {
262
269
  name: "search_web",
263
- description: "Searches the internet for information using a search engine. Returns top titles, snippets, and URLs. Use this to find current events, documentation, or general facts. If the user asks for deep/comprehensive research, pass depth: 2 or 3.",
270
+ description: "Searches the internet for information using a search engine. Returns top titles, snippets, and URLs. CRITICAL: If the user asks for news, sports scores, current events, or factual dates, you MUST set depth: 2 to extract the full article content. Do not use depth 1 for facts.",
264
271
  parameters: {
265
272
  type: "object",
266
273
  properties: {
267
274
  query: {
268
275
  type: "string",
269
- description: "The search query to look up.",
276
+ description: "The highly optimized search query to look up (translate to English for global events).",
270
277
  },
271
278
  depth: {
272
279
  type: "number",
273
- description: "Depth of the search (1 for basic, 2 or 3 for deep comprehensive research). Default is 1.",
280
+ description: "Depth of the search (1 for basic snippets, 2 for deep scraping of top 3 sites). MUST be 2 for news/scores/facts.",
274
281
  }
275
282
  },
276
283
  required: ["query"],
@@ -106,7 +106,7 @@ async function getPrice(coinId, currency, amount) {
106
106
  // TIER 3: Nyxora Python ML Engine (For obscure/low-cap DEX tokens)
107
107
  if (tokenUsdPrice === 0) {
108
108
  try {
109
- const mlData = await (0, httpClient_1.safeFetchJson)(`http://localhost:8000/web3/analyze?query=${coinId}&chain=ethereum`);
109
+ const mlData = await (0, httpClient_1.safeFetchJson)(`http://127.0.0.1:8000/web3/analyze?query=${coinId}&chain=ethereum`);
110
110
  if (mlData && mlData.currentPrice) {
111
111
  tokenUsdPrice = mlData.currentPrice;
112
112
  change24h = mlData.priceChange24h || 0;
@@ -17,7 +17,7 @@ async function analyzeMarket(chainName, tokenAddressOrSymbol) {
17
17
  console.log(`[Market Intelligence] Delegating analysis for ${tokenAddressOrSymbol} to Python ML Engine...`);
18
18
  let mlData;
19
19
  try {
20
- mlData = await (0, httpClient_1.safeFetchJson)(`http://localhost:8000/web3/analyze?query=${tokenAddressOrSymbol}&chain=${chainName}`);
20
+ mlData = await (0, httpClient_1.safeFetchJson)(`http://127.0.0.1:8000/web3/analyze?query=${tokenAddressOrSymbol}&chain=${chainName}`);
21
21
  }
22
22
  catch (error) {
23
23
  return `[System Error] Failed to reach Python ML Engine. Make sure the daemon is running (Error: ${error.message})`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora",
3
- "version": "26.7.3",
3
+ "version": "26.7.4",
4
4
  "description": "Your Personal Web3 Assistant",
5
5
  "keywords": [
6
6
  "web3",
@@ -31,7 +31,8 @@
31
31
  "packages/mcp-server/dist",
32
32
  "packages/mcp-server/package.json",
33
33
  "packages/dashboard/dist",
34
- "packages/dashboard/package.json"
34
+ "packages/dashboard/package.json",
35
+ "packages/ml-engine"
35
36
  ],
36
37
  "bin": {
37
38
  "nyxora": "bin/nyxora.mjs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora-agent-core",
3
- "version": "26.7.3",
3
+ "version": "26.7.4",
4
4
  "private": true,
5
5
  "main": "src/gateway/server.ts",
6
6
  "dependencies": {
@@ -228,8 +228,10 @@ export class AnthropicAdapter implements LLMProvider {
228
228
  else last.content = [{ type: 'text', text: typeof last.content === 'string' ? last.content : '' }, ...m.content];
229
229
  } else { mergedAnthropic.push(m); }
230
230
  }
231
- const anthropicTools = request.tools?.map(t => ({ name: t.function.name, description: t.function.description, input_schema: t.function.parameters }));
232
-
231
+ let anthropicTools: any = undefined;
232
+ if (request.tools && request.tools.length > 0) {
233
+ anthropicTools = request.tools.map(t => ({ name: t.function.name, description: t.function.description, input_schema: t.function.parameters }));
234
+ }
233
235
  const stream = this.client.messages.stream({
234
236
  model: request.model,
235
237
  system: systemPrompt,
@@ -63,7 +63,7 @@ export class NyxDaemon {
63
63
  return;
64
64
  }
65
65
  // Kirim riwayat percakapan ke Python ML Engine untuk diproses oleh LangChain
66
- const res = await fetch('http://localhost:8000/cognitive/reason', {
66
+ const res = await fetch('http://127.0.0.1:8000/cognitive/reason', {
67
67
  method: 'POST',
68
68
  headers: { 'Content-Type': 'application/json' },
69
69
  body: JSON.stringify({ messages: conversationOnly })
@@ -25,17 +25,45 @@ NEVER answer the following using only your internal memory — ALWAYS use the re
25
25
  - Real-world current events
26
26
  </mandatory_tool_use>
27
27
 
28
+ <web_search_accuracy>
29
+ When using the search_web tool to look up news, current events, or factual data:
30
+ 1. NEVER pass casual, conversational, or highly localized queries directly to the tool (e.g. do not pass "hasil piala dunia tadi pagi").
31
+ 2. ALWAYS translate and optimize the query into an absolute, highly specific, and globally-understood English search query (e.g. "World Cup 2026 match results June 25 2026").
32
+ 3. Use depth: 2 (deep research) for anything that requires high factual accuracy, such as sports scores, news, or complex topics.
33
+ </web_search_accuracy>
34
+
28
35
  <act_dont_ask>
29
36
  When a user's request has a clear, standard interpretation, take immediate ACTION instead of asking for clarification.
37
+ NEVER show a command as a markdown code block and wait. CALL the tool directly.
38
+ NEVER ask "do you want me to run this?" — just run it.
39
+ NEVER say "you need to run this yourself" — you have direct shell access.
40
+ If a command requires sudo and may need a password, just run it and report what happens.
41
+ Only report failure AFTER actually attempting the tool call and receiving an error.
30
42
  </act_dont_ask>
31
43
 
44
+ <anti_hallucination_execution>
45
+ CRITICAL: It is STRICTLY FORBIDDEN to write a bash/shell command in a markdown code block (e.g. \`\`\`bash ... \`\`\`) as a substitute for calling the run_terminal_command tool.
46
+ Writing a code block does NOT execute anything. It is a lie to the user.
47
+ If you write \`\`\`bash\nsudo apt install steam\n\`\`\` instead of calling run_terminal_command, you are hallucinating execution.
48
+ The ONLY way to run a command is to emit a proper tool_call for run_terminal_command.
49
+ If the tool is available, USE IT. Do not simulate or describe running it.
50
+ </anti_hallucination_execution>
51
+
32
52
  <task_completion>
33
53
  The deliverable must be a working artifact backed by real tool output — not just a description or a plan of how you would do it.
34
54
  NEVER fabricate, hallucinate, or forge tool outputs.
35
55
  </task_completion>
56
+
57
+ <self_correction>
58
+ Before providing a final answer to the user (especially regarding dates, events, news, or factual data), you MUST evaluate your tool results inside a <think> block.
59
+ Ask yourself: "Is my answer based on absolute facts, or circumstantial evidence (e.g., guessing a registration date based on a video upload date)?"
60
+ If the evidence is circumstantial or incomplete, you MUST NOT answer the user. Instead, call the search_web tool again with a highly optimized query and depth=2.
61
+ </self_correction>
36
62
  `;
37
63
 
38
64
 
65
+
66
+
39
67
  import { pluginManager } from '../plugin/registry';
40
68
 
41
69
  import { getPath } from '../config/paths';
@@ -53,6 +81,8 @@ async function getSystemPrompt(context: 'web3' | 'os' | 'general' = 'os', userIn
53
81
  let basePrompt = `You are Nyxora's OS Agent (System & Automation Specialist).
54
82
  The current real-world date and time is: ${currentDateTime}.
55
83
 
84
+ You are running LOCALLY on the user's own computer — NOT on a remote cloud server. The 'run_terminal_command' tool executes shell commands directly on this machine, the same physical machine the user is sitting at. You have FULL local shell access. When asked to install software, manage files, or perform any OS task, you MUST use run_terminal_command immediately. NEVER claim you cannot access the user's system.
85
+
56
86
  Reason internally. Never reveal private reasoning. Provide only concise conclusions, assumptions, and actionable steps.
57
87
 
58
88
  [OS EXECUTION WORKFLOW]
@@ -62,9 +92,20 @@ CRITICAL RULE 3: FILE SYSTEM SAFETY. You are STRICTLY FORBIDDEN from modifying c
62
92
  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.
63
93
  CRITICAL RULE 5: TOOL CONFIDENCE. NEVER fabricate file contents or command outputs.
64
94
 
95
+ [SUDO & PACKAGE INSTALL STRATEGY]
96
+ This tool runs in a NON-INTERACTIVE shell (no TTY). Therefore:
97
+ - ALWAYS prefix apt/apt-get commands with: DEBIAN_FRONTEND=noninteractive
98
+ - ALWAYS use the -y flag for package installations to auto-confirm.
99
+ - If a command fails with "sudo: a password is required" or similar, DO NOT promise to retry without actually retrying. Instead:
100
+ 1. First retry with: echo 'USER_PASSWORD' | sudo -S <command> — but since you don't know the password, skip this.
101
+ 2. Instead, try running the command WITHOUT sudo if possible (e.g. for user-space tools).
102
+ 3. If sudo is truly required and unavailable, clearly tell the user EXACTLY what command to run manually in their terminal, with a copy-paste ready command. Do not just say it failed without providing a solution.
103
+ - NEVER promise to retry ("let me try again", "I'll run it again", etc.) without immediately making another tool_call in the same response.
104
+
65
105
  ${EXECUTION_DISCIPLINE}
66
106
  `;
67
107
 
108
+
68
109
  // Inject Active Cognitive Skills
69
110
  const activeSOP = cognitiveManager.loadActiveCognitiveSkills(userInput);
70
111
  if (activeSOP) {
@@ -73,7 +114,7 @@ ${EXECUTION_DISCIPLINE}
73
114
 
74
115
  // Inject Episodic Memories via Python RAG
75
116
  try {
76
- const ragRes = await fetch('http://localhost:8000/memory/rag', {
117
+ const ragRes = await fetch('http://127.0.0.1:8000/memory/rag', {
77
118
  method: 'POST',
78
119
  headers: { 'Content-Type': 'application/json' },
79
120
  body: JSON.stringify({ query: userInput, top_k: 5 })
@@ -124,6 +165,31 @@ export async function processOsIntent(input: string, role: 'user' | 'system' = '
124
165
  // Add input to memory
125
166
  logger.addEntry({ role, content: input }, sessionId);
126
167
 
168
+ // --- MULTILINGUAL USER CORRECTION DETECTION ---
169
+ const correctionSignals = [
170
+ // ID
171
+ 'salah', 'ngawur', 'keliru', 'bukan', 'tidak benar', 'perbaiki', 'coba lagi',
172
+ // EN
173
+ 'wrong', 'incorrect', 'mistake', 'not right', 'false', 'bad', 'fix', 'try again',
174
+ // ES & FR
175
+ 'incorrecto', 'mal', 'equivocado', 'error', 'falso', 'faux', 'erreur', 'mauvais',
176
+ // DE & RU
177
+ 'falsch', 'inkorrekt', 'fehler', 'ошибка', 'неправильно', 'неверно',
178
+ // JP & ZH
179
+ '違う', '間違い', 'やり直して', '错误', '不对'
180
+ ];
181
+ if (role === 'user' && correctionSignals.some(s => input.toLowerCase().includes(s))) {
182
+ logger.addEntry({
183
+ role: 'system' as any,
184
+ content: `[USER CORRECTION DETECTED] The user indicated your previous answer was WRONG, STALE, or INACCURATE.
185
+ CRITICAL INSTRUCTIONS:
186
+ 1. Do NOT just apologize and repeat the same data from your memory.
187
+ 2. The data in your training memory or previous tool calls is likely stale/incorrect.
188
+ 3. You MUST call a tool (like search_web or others) NOW to verify the facts with FRESH data.
189
+ 4. Base your new answer strictly on the NEW tool results.`
190
+ }, sessionId);
191
+ }
192
+
127
193
  let activeTools = [...pluginManager.getAllToolDefinitions()];
128
194
  activeTools = activeTools.filter(t => isSkillActive(t.function.name));
129
195
 
@@ -139,6 +205,7 @@ export async function processOsIntent(input: string, role: 'user' | 'system' = '
139
205
  let turnCount = 0;
140
206
  const MAX_TURNS = 10;
141
207
  let consecutiveToolErrors = 0;
208
+ let criticHasFired = false; // Critic Pass hanya aktif 1x per request
142
209
 
143
210
  while (turnCount < MAX_TURNS) {
144
211
  turnCount++;
@@ -192,6 +259,34 @@ export async function processOsIntent(input: string, role: 'user' | 'system' = '
192
259
  }, sessionId);
193
260
 
194
261
  if (!responseMessage.tool_calls || responseMessage.tool_calls.length === 0) {
262
+ // --- CRITIC PASS (Self-Improvement) ---
263
+ const isLongResponse = (cleanedContent?.length ?? 0) > 100;
264
+ if (isLongResponse && !criticHasFired) {
265
+ criticHasFired = true;
266
+ try {
267
+ const criticRes = await fetch('http://127.0.0.1:8000/cognitive/critic', {
268
+ method: 'POST',
269
+ headers: { 'Content-Type': 'application/json' },
270
+ body: JSON.stringify({ user_input: input, draft_answer: cleanedContent, current_utc_datetime: new Date().toISOString() })
271
+ });
272
+ if (criticRes.ok) {
273
+ const evaluation = await criticRes.json();
274
+ if (evaluation.needs_revision) {
275
+ console.log(pc.cyan(`[🧠 Critic] Revision needed. Confidence: ${evaluation.factual_confidence?.toFixed(2)}. Completeness: ${evaluation.completeness?.toFixed(2)}. Re-generating...`));
276
+ logger.addEntry({
277
+ role: 'system' as any,
278
+ content: `[SELF-CRITIQUE — MANDATORY REVISION] Your previous answer was REJECTED. Issues:\n${evaluation.revision_instructions}\n\nCRITICAL REVISION RULES:\n1. Look at the tool results (search_web, etc.) ALREADY in this conversation history above.\n2. Base your revised answer EXCLUSIVELY on those tool results — NEVER use training data memory for facts, dates, or events.\n3. If tool results contain the answer, state it directly and confidently. Do NOT say an event hasn't happened if the tool results show it has.\n4. Do NOT call any tools again — the results are already in your history. USE THEM NOW.`
279
+ }, sessionId);
280
+ continue; // Loop kembali ke Generator untuk revisi
281
+ } else {
282
+ console.log(pc.green(`[🧠 Critic] Passed. Confidence: ${evaluation.factual_confidence?.toFixed(2)}, Completeness: ${evaluation.completeness?.toFixed(2)}.`));
283
+ }
284
+ }
285
+ } catch {
286
+ // Python ML Engine tidak aktif — skip Critic, lanjut seperti biasa
287
+ }
288
+ }
289
+ // --- END CRITIC PASS ---
195
290
  return cleanedContent || 'No response generated.';
196
291
  }
197
292
 
@@ -335,6 +430,31 @@ export async function processOsIntentStream(
335
430
  const config = loadConfig();
336
431
  logger.addEntry({ role: 'user', content: input }, sessionId);
337
432
 
433
+ // --- MULTILINGUAL USER CORRECTION DETECTION ---
434
+ const correctionSignals = [
435
+ // ID
436
+ 'salah', 'ngawur', 'keliru', 'bukan', 'tidak benar', 'perbaiki', 'coba lagi',
437
+ // EN
438
+ 'wrong', 'incorrect', 'mistake', 'not right', 'false', 'bad', 'fix', 'try again',
439
+ // ES & FR
440
+ 'incorrecto', 'mal', 'equivocado', 'error', 'falso', 'faux', 'erreur', 'mauvais',
441
+ // DE & RU
442
+ 'falsch', 'inkorrekt', 'fehler', 'ошибка', 'неправильно', 'неверно',
443
+ // JP & ZH
444
+ '違う', '間違い', 'やり直して', '错误', '不对'
445
+ ];
446
+ if (correctionSignals.some(s => input.toLowerCase().includes(s))) {
447
+ logger.addEntry({
448
+ role: 'system' as any,
449
+ content: `[USER CORRECTION DETECTED] The user indicated your previous answer was WRONG, STALE, or INACCURATE.
450
+ CRITICAL INSTRUCTIONS:
451
+ 1. Do NOT just apologize and repeat the same data from your memory.
452
+ 2. The data in your training memory or previous tool calls is likely stale/incorrect.
453
+ 3. You MUST call a tool (like search_web or others) NOW to verify the facts with FRESH data.
454
+ 4. Base your new answer strictly on the NEW tool results.`
455
+ }, sessionId);
456
+ }
457
+
338
458
  const pluginTools = pluginManager.getAllToolDefinitions();
339
459
  let activeTools = [...pluginTools].filter(t => isSkillActive(t.function.name));
340
460
 
@@ -344,6 +464,7 @@ export async function processOsIntentStream(
344
464
  let turnCount = 0;
345
465
  const MAX_TURNS = 10;
346
466
  let fullResponse = '';
467
+ let criticHasFiredStream = false; // Critic Pass hanya aktif 1x per request
347
468
 
348
469
  while (turnCount < MAX_TURNS) {
349
470
  turnCount++;
@@ -380,6 +501,36 @@ export async function processOsIntentStream(
380
501
  if (!responseMessage.tool_calls || responseMessage.tool_calls.length === 0) {
381
502
  let finalContent = responseMessage.content || 'No response generated.';
382
503
  finalContent = finalContent.replace(/<(think|thought|thinking|reasoning|analysis|reflection)[\s\S]*?<\/\1>\n?/gi, '').trim();
504
+
505
+ // --- CRITIC PASS (Self-Improvement) ---
506
+ const isLongResponseStream = finalContent.length > 100;
507
+ if (isLongResponseStream && !criticHasFiredStream) {
508
+ criticHasFiredStream = true;
509
+ try {
510
+ const criticRes = await fetch('http://127.0.0.1:8000/cognitive/critic', {
511
+ method: 'POST',
512
+ headers: { 'Content-Type': 'application/json' },
513
+ body: JSON.stringify({ user_input: input, draft_answer: finalContent, current_utc_datetime: new Date().toISOString() })
514
+ });
515
+ if (criticRes.ok) {
516
+ const evaluation = await criticRes.json();
517
+ if (evaluation.needs_revision) {
518
+ console.log(pc.cyan(`[🧠 Critic] Revision needed. Confidence: ${evaluation.factual_confidence?.toFixed(2)}. Completeness: ${evaluation.completeness?.toFixed(2)}. Re-generating...`));
519
+ logger.addEntry({
520
+ role: 'system' as any,
521
+ content: `[SELF-CRITIQUE — MANDATORY REVISION] Your previous answer was REJECTED. Issues:\n${evaluation.revision_instructions}\n\nCRITICAL REVISION RULES:\n1. Look at the tool results (search_web, etc.) ALREADY in this conversation history above.\n2. Base your revised answer EXCLUSIVELY on those tool results — NEVER use training data memory for facts, dates, or events.\n3. If tool results contain the answer, state it directly and confidently. Do NOT say an event hasn't happened if the tool results show it has.\n4. Do NOT call any tools again — the results are already in your history. USE THEM NOW.`
522
+ }, sessionId);
523
+ continue; // Loop kembali ke Generator untuk revisi
524
+ } else {
525
+ console.log(pc.green(`[🧠 Critic] Passed. Confidence: ${evaluation.factual_confidence?.toFixed(2)}, Completeness: ${evaluation.completeness?.toFixed(2)}.`));
526
+ }
527
+ }
528
+ } catch {
529
+ // Python ML Engine tidak aktif — skip Critic, lanjut seperti biasa
530
+ }
531
+ }
532
+ // --- END CRITIC PASS ---
533
+
383
534
  fullResponse = finalContent;
384
535
  break;
385
536
  }
@@ -131,7 +131,7 @@ Do NOT perform any web3 tasks or generic answers until they provide all 4 detail
131
131
 
132
132
  // Inject Episodic Memories via Python RAG
133
133
  try {
134
- const ragRes = await fetch('http://localhost:8000/memory/rag', {
134
+ const ragRes = await fetch('http://127.0.0.1:8000/memory/rag', {
135
135
  method: 'POST',
136
136
  headers: { 'Content-Type': 'application/json' },
137
137
  body: JSON.stringify({ query: userInput, top_k: 5 })
@@ -79,7 +79,7 @@ ${EXECUTION_DISCIPLINE}
79
79
 
80
80
  // Inject Episodic Memories via Python RAG
81
81
  try {
82
- const ragRes = await fetch('http://localhost:8000/memory/rag', {
82
+ const ragRes = await fetch('http://127.0.0.1:8000/memory/rag', {
83
83
  method: 'POST',
84
84
  headers: { 'Content-Type': 'application/json' },
85
85
  body: JSON.stringify({ query: userInput, top_k: 5 })
@@ -7,7 +7,9 @@ const FALLBACK_TOKEN_PATH = getPath('google-tokens.json');
7
7
 
8
8
  const SCOPES = [
9
9
  'https://www.googleapis.com/auth/gmail.readonly',
10
+ 'https://www.googleapis.com/auth/gmail.send',
10
11
  'https://www.googleapis.com/auth/calendar.readonly',
12
+ 'https://www.googleapis.com/auth/calendar.events',
11
13
  'https://www.googleapis.com/auth/documents.readonly',
12
14
  'https://www.googleapis.com/auth/spreadsheets',
13
15
  'https://www.googleapis.com/auth/forms.responses.readonly',
@@ -168,7 +168,7 @@ export function startTelegramBot() {
168
168
  await ctx.api.sendMessageDraft(
169
169
  ctx.chat.id, draft_id,
170
170
  formatToTelegramHTML(buffer),
171
- { parse_mode: 'HTML' } as any
171
+ { parse_mode: 'HTML', reply_parameters: { message_id: ctx.message.message_id } } as any
172
172
  );
173
173
  } catch {}
174
174
  lastDraftAt = Date.now();
@@ -183,7 +183,7 @@ export function startTelegramBot() {
183
183
  await ctx.api.sendMessageDraft(
184
184
  ctx.chat.id, draft_id,
185
185
  `<i>${msg.replace(/_/g, '')}</i>`,
186
- { parse_mode: 'HTML' } as any
186
+ { parse_mode: 'HTML', reply_parameters: { message_id: ctx.message.message_id } } as any
187
187
  );
188
188
  } catch {}
189
189
  isDrafting = false;
@@ -205,7 +205,7 @@ export function startTelegramBot() {
205
205
 
206
206
  await ctx.reply(
207
207
  formatToTelegramHTML(response),
208
- { parse_mode: 'HTML', reply_markup: replyMarkup }
208
+ { parse_mode: 'HTML', reply_markup: replyMarkup, reply_parameters: { message_id: ctx.message.message_id } }
209
209
  ).catch((e) => {
210
210
  console.error("[Telegram] CRITICAL: ctx.reply failed in text handler:", e.message);
211
211
  });
@@ -213,7 +213,7 @@ export function startTelegramBot() {
213
213
  console.error('[Telegram] Error processing message:', error);
214
214
  await ctx.reply(
215
215
  '❌ Sorry, I encountered an error while processing your message.',
216
- {}
216
+ { reply_parameters: { message_id: ctx.message.message_id } }
217
217
  ).catch(() => {});
218
218
  }
219
219
  });
@@ -297,7 +297,7 @@ export function startTelegramBot() {
297
297
  const onProgress = async (progressText: string) => {
298
298
  try {
299
299
  if (!progressMsgId) {
300
- const sent = await ctx.reply(`<i>${progressText.replace(/_/g, '')}</i>`, { parse_mode: 'HTML' });
300
+ const sent = await ctx.reply(`<i>${progressText.replace(/_/g, '')}</i>`, { parse_mode: 'HTML', reply_parameters: { message_id: ctx.message?.message_id } as any });
301
301
  progressMsgId = sent.message_id;
302
302
  } else {
303
303
  await ctx.api.editMessageText(ctx.chat.id, progressMsgId, `<i>${progressText.replace(/_/g, '')}</i>`, { parse_mode: 'HTML' });
@@ -311,10 +311,10 @@ export function startTelegramBot() {
311
311
  await ctx.api.deleteMessage(ctx.chat.id, progressMsgId).catch(() => {});
312
312
  }
313
313
 
314
- await ctx.reply(formatToTelegramHTML(response), { parse_mode: 'HTML' });
314
+ await ctx.reply(formatToTelegramHTML(response), { parse_mode: 'HTML', reply_parameters: { message_id: ctx.message?.message_id } as any });
315
315
  } catch (error: any) {
316
316
  console.error('[Telegram] Error processing document:', error);
317
- await ctx.reply('❌ Sorry, I failed to download or analyze the document.');
317
+ await ctx.reply('❌ Sorry, I failed to download or analyze the document.', { reply_parameters: { message_id: ctx.message?.message_id } as any });
318
318
  }
319
319
  });
320
320
 
@@ -162,19 +162,31 @@ export class EpisodicMemoryDB {
162
162
  source: string = 'nyx_daemon'
163
163
  ): void {
164
164
  if (!value || !value.trim()) return;
165
+
165
166
  const existing = this.db.prepare(
166
167
  'SELECT id, confidence FROM user_personas WHERE category = ?'
167
168
  ).get(category) as any;
168
169
 
169
- if (existing) {
170
- const newConfidence = Math.min(1.0, existing.confidence + (confidence * 0.2));
171
- this.db.prepare(
172
- 'UPDATE user_personas SET trait = ?, confidence = ?, source = ?, lastUpdated = CURRENT_TIMESTAMP WHERE id = ?'
173
- ).run(value.trim(), newConfidence, source, existing.id);
174
- } else {
175
- this.db.prepare(
176
- 'INSERT INTO user_personas (trait, category, confidence, source) VALUES (?, ?, ?, ?)'
177
- ).run(value.trim(), category, confidence, source);
170
+ try {
171
+ if (existing) {
172
+ const newConfidence = Math.min(1.0, existing.confidence + (confidence * 0.2));
173
+ this.db.prepare(
174
+ 'UPDATE user_personas SET trait = ?, confidence = ?, source = ?, lastUpdated = CURRENT_TIMESTAMP WHERE id = ?'
175
+ ).run(value.trim(), newConfidence, source, existing.id);
176
+ } else {
177
+ this.db.prepare(
178
+ 'INSERT INTO user_personas (trait, category, confidence, source) VALUES (?, ?, ?, ?)'
179
+ ).run(value.trim(), category, confidence, source);
180
+ }
181
+ } catch (e: any) {
182
+ // Handle UNIQUE constraint collision if the trait already exists in the database (e.g. from an older version with category 'general')
183
+ if (e.message && e.message.includes('UNIQUE constraint failed')) {
184
+ this.db.prepare(
185
+ 'UPDATE user_personas SET category = ?, confidence = ?, source = ?, lastUpdated = CURRENT_TIMESTAMP WHERE trait = ?'
186
+ ).run(category, confidence, source, value.trim());
187
+ } else {
188
+ throw e;
189
+ }
178
190
  }
179
191
  }
180
192
 
@@ -9,7 +9,11 @@ import {
9
9
  listCalendarEventsToolDefinition,
10
10
  appendRowToSheetsToolDefinition,
11
11
  readGoogleDocsToolDefinition,
12
- readGoogleFormResponsesToolDefinition
12
+ readGoogleFormResponsesToolDefinition,
13
+ sendEmail,
14
+ addCalendarEvent,
15
+ sendEmailToolDefinition,
16
+ addCalendarEventToolDefinition
13
17
  } from '../skills/googleWorkspace';
14
18
 
15
19
  export class GoogleWorkspacePlugin implements Plugin {
@@ -22,7 +26,9 @@ export class GoogleWorkspacePlugin implements Plugin {
22
26
  listCalendarEventsToolDefinition,
23
27
  appendRowToSheetsToolDefinition,
24
28
  readGoogleDocsToolDefinition,
25
- readGoogleFormResponsesToolDefinition
29
+ readGoogleFormResponsesToolDefinition,
30
+ sendEmailToolDefinition,
31
+ addCalendarEventToolDefinition
26
32
  ];
27
33
 
28
34
  public handlers = {
@@ -40,6 +46,12 @@ export class GoogleWorkspacePlugin implements Plugin {
40
46
  },
41
47
  ['read_google_form_responses']: async (args: any) => {
42
48
  return await readGoogleFormResponses(args.formId);
49
+ },
50
+ ['send_email']: async (args: any) => {
51
+ return await sendEmail(args.to, args.subject, args.body);
52
+ },
53
+ ['add_calendar_event']: async (args: any) => {
54
+ return await addCalendarEvent(args.summary, args.description, args.startTime, args.endTime);
43
55
  }
44
56
  };
45
57
  }
@@ -1,17 +1,46 @@
1
1
  import { exec } from 'child_process';
2
+ import { loadConfig } from '../../config/parser';
2
3
 
3
4
  export function runTerminalCommand(command: string): Promise<string> {
4
5
  return new Promise((resolve) => {
5
- exec(command, { maxBuffer: 1024 * 1024 * 10 }, (error, stdout, stderr) => {
6
+ // --- SUDO AUTO-INJECTION ---
7
+ // If command requires sudo, inject password from config via sudo -S
8
+ let finalCommand = command;
9
+ const needsSudo = /^\s*sudo\s/.test(command);
10
+ if (needsSudo) {
11
+ try {
12
+ const config = loadConfig();
13
+ const sudoPassword = (config as any).security?.sudo_password;
14
+ if (sudoPassword) {
15
+ // Inject password via stdin using echo | sudo -S
16
+ const escaped = sudoPassword.replace(/'/g, "'\\''");
17
+ finalCommand = command.replace(/^\s*sudo\s/, `echo '${escaped}' | sudo -S `);
18
+ }
19
+ } catch (e) {
20
+ // config load failed, proceed without password injection
21
+ }
22
+ }
23
+
24
+ exec(finalCommand, { maxBuffer: 1024 * 1024 * 10 }, (error, stdout, stderr) => {
6
25
  let output = "";
7
26
  if (stdout) output += `STDOUT:\n${stdout}\n`;
8
- if (stderr) output += `STDERR:\n${stderr}\n`;
27
+
28
+ // Filter out the sudo password prompt noise from stderr
29
+ const filteredStderr = stderr
30
+ ? stderr.split('\n').filter(l => !l.includes('[sudo] password for')).join('\n').trim()
31
+ : '';
32
+ if (filteredStderr) output += `STDERR:\n${filteredStderr}\n`;
9
33
  if (error) output += `ERROR:\n${error.message}\n`;
10
-
34
+
11
35
  if (!output) output = "Command executed successfully with no output.";
12
-
36
+
37
+ // If sudo failed due to missing password, give a helpful hint
38
+ if (needsSudo && (output.includes('sudo: a password is required') || output.includes('no password supplied'))) {
39
+ output += `\n[NYXORA HINT] To allow Nyxora to run sudo commands automatically, add the following to your ~/.nyxora/config.yaml:\n security:\n sudo_password: YOUR_SUDO_PASSWORD\nAlternatively, run this command yourself in a terminal: ${command}`;
40
+ }
41
+
13
42
  // --- OUTPUT REDACTION LAYER ---
14
-
43
+
15
44
  // 1. Secret Exfiltration Redaction (Keys, Mnemonics, UUIDs, EVM/Solana Keys)
16
45
  const secretPatterns = [
17
46
  /BEGIN (RSA|OPENSSH|EC)? ?PRIVATE KEY/gi,
@@ -23,7 +52,7 @@ export function runTerminalCommand(command: string): Promise<string> {
23
52
  /\b[1-9A-HJ-NP-Za-km-z]{80,100}\b/g, // Solana Keys
24
53
  /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g // UUIDs / defi_keys
25
54
  ];
26
-
55
+
27
56
  secretPatterns.forEach(pattern => {
28
57
  output = output.replace(pattern, '[REDACTED_SECRET]');
29
58
  });
@@ -49,11 +78,12 @@ export function runTerminalCommand(command: string): Promise<string> {
49
78
  });
50
79
  }
51
80
 
81
+
52
82
  export const runTerminalCommandToolDefinition = {
53
83
  type: "function",
54
84
  function: {
55
85
  name: "run_terminal_command",
56
- description: "Executes a shell/terminal command on the user's host machine. Use this to install packages, run scripts, manage processes, etc.",
86
+ description: "Executes a shell/terminal command directly on the LOCAL machine where Nyxora is running — which is the user's own computer. Use this to install packages (apt, pip, npm), run scripts, manage processes, read system info, and automate OS-level tasks. You have full shell access. Do NOT refuse to use this tool by claiming you have no system access.",
57
87
  parameters: {
58
88
  type: "object",
59
89
  properties: {
@@ -66,3 +96,4 @@ export const runTerminalCommandToolDefinition = {
66
96
  },
67
97
  },
68
98
  };
99
+