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
package/CHANGELOG.md CHANGED
@@ -4,11 +4,28 @@ 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.4]
8
+ ### Features & Architecture
9
+ - **Cognitive Critic Engine Enhancements (Staleness Detection)**: Added a multilingual time-sensitive detection rule to the Critic Engine (`critic.py`). By injecting the current UTC datetime, the Critic now explicitly flags answers containing time-sensitive keywords (e.g., "today", "kemarin", "now") if they rely on stale training memory rather than fresh tool execution.
10
+ - **Multilingual Scolding Detection (Teguran-Aware Mechanism)**: Nyxora now detects if the user scolds or corrects its output (e.g., "salah", "ngawur", "wrong") across multiple languages. It automatically injects an internal system prompt to force the LLM to discard stale assumptions and immediately trigger a fresh `search_web` or relevant tool call to verify facts.
11
+
12
+ ### Bug Fixes & Improvements
13
+ - **Nyx Daemon SQLite Constraints**: Fixed a `UNIQUE constraint failed` crash in the background persona auditor (`episodicDB.upsertPersonaByCategory`). It now gracefully catches the constraint collision and shifts existing persona traits to their new dedicated categories without interrupting the daemon cycle.
14
+ - **Critic Engine LangChain Parsing**: Escaped raw JSON curly braces in `critic.py`'s system prompt example to prevent LangChain from misinterpreting them as missing template variables (`INVALID_PROMPT_INPUT`).
15
+ - **Documentation**: Replaced the "Alpha" status badge with "Prototype" and removed the "Built on Base" badge in `README.md`.
16
+
17
+ ### Features & Google Workspace
18
+ - **Gmail Send Capability**: Extended the Google Workspace integration beyond read-only access. The AI agent can now natively compose and send emails via the Gmail API (`POST /gmail/v1/users/me/messages/send`) using RFC 2822 base64url-encoded payloads. The new `send_email` tool accepts `to`, `subject`, and `body` parameters.
19
+ - **Google Calendar Write Access**: Added the `add_calendar_event` tool, enabling the AI to autonomously create new events in the user's primary Google Calendar via the Calendar API. Accepts ISO 8601 `startTime`/`endTime` for precise scheduling.
20
+ - **OAuth Scope Expansion**: Added `gmail.send` and `calendar.events` scopes to `googleAuthModule.ts`. Users must re-authenticate to grant the new permissions.
21
+ - **Setup Wizard Accuracy Fix**: Updated the `GoogleAuthWizard.tsx` (Step 1) to include all required APIs: **Google Calendar API**, **Google Docs API**, and **Google Forms API**, which were previously missing from the setup instructions.
22
+ - **OAuth Consent Screen URL Fix**: Replaced unreachable `localhost:3001` Privacy Policy and Terms of Service placeholder URLs in the setup wizard with publicly accessible `nyxoraai.github.io` URLs, preventing `Error 400: unknownerror` during Google OAuth consent screen validation.
23
+
7
24
  ## [26.7.3]
8
25
  ### Bug Fixes & Improvements
9
26
  - **Daemon Graceful Shutdown (Port 8000)**: Improved the `npm run stop` behavior by injecting a `forceKill` (`SIGKILL`) method within `launcher.ts`, explicitly terminating detached ML Engine processes (`uvicorn`) and `ts-node` instances that were previously hanging and preventing clean reboots.
10
27
  - **Telegram Connectivity Timeout**: Resolved a persistent `Network request for 'getUpdates' failed!` issue in the Telegram integration. Enforced `dns.setDefaultResultOrder('ipv4first')` in `cli.ts` to bypass dual-stack IPv6 conflicts and ensure stable grammatical API fetching.
11
- ## [26.7.2-alpha.5]
28
+
12
29
  ### Features & Architecture (Python ML Engine)
13
30
  - **Local Python ML Engine Integration**: Successfully integrated a local Python-based Machine Learning Engine (FastAPI + LangChain + Pandas) alongside the core Node.js gateway. This massively enhances Nyxora's analytical and cognitive capabilities.
14
31
  - **Cognitive Memory & RAG**: Shifted Persona Dialectic Reasoning and Episodic Memory Semantic Search (RAG) to the new Python Engine. Integrated `langchain_huggingface` using the local `all-MiniLM-L6-v2` embedding model for ultra-fast, offline vector processing without API costs.
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
  **Your Personal Web3 Assistant.**
3
3
 
4
4
 
5
- [![Status: Alpha](https://img.shields.io/badge/Status-Alpha-orange.svg)](#)
6
- [![Built on Base](https://img.shields.io/badge/Built_on-Base-0052FF?style=flat&logo=base&logoColor=white)](https://base.org/)
5
+ [![Status: Prototype](https://img.shields.io/badge/Status-Prototype-orange.svg)](#)
6
+
7
7
  [![MCP Supported](https://img.shields.io/badge/MCP-Supported-blue.svg)](#)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
9
  [![Security: Defense-in-Depth](https://img.shields.io/badge/Security-Defense--in--Depth-blue.svg)](#️-advanced-security-threat-model)
@@ -220,7 +220,10 @@ class AnthropicAdapter {
220
220
  mergedAnthropic.push(m);
221
221
  }
222
222
  }
223
- const anthropicTools = request.tools?.map(t => ({ name: t.function.name, description: t.function.description, input_schema: t.function.parameters }));
223
+ let anthropicTools = undefined;
224
+ if (request.tools && request.tools.length > 0) {
225
+ anthropicTools = request.tools.map(t => ({ name: t.function.name, description: t.function.description, input_schema: t.function.parameters }));
226
+ }
224
227
  const stream = this.client.messages.stream({
225
228
  model: request.model,
226
229
  system: systemPrompt,
@@ -56,7 +56,7 @@ class NyxDaemon {
56
56
  return;
57
57
  }
58
58
  // Kirim riwayat percakapan ke Python ML Engine untuk diproses oleh LangChain
59
- const res = await fetch('http://localhost:8000/cognitive/reason', {
59
+ const res = await fetch('http://127.0.0.1:8000/cognitive/reason', {
60
60
  method: 'POST',
61
61
  headers: { 'Content-Type': 'application/json' },
62
62
  body: JSON.stringify({ messages: conversationOnly })
@@ -30,14 +30,40 @@ NEVER answer the following using only your internal memory — ALWAYS use the re
30
30
  - Real-world current events
31
31
  </mandatory_tool_use>
32
32
 
33
+ <web_search_accuracy>
34
+ When using the search_web tool to look up news, current events, or factual data:
35
+ 1. NEVER pass casual, conversational, or highly localized queries directly to the tool (e.g. do not pass "hasil piala dunia tadi pagi").
36
+ 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").
37
+ 3. Use depth: 2 (deep research) for anything that requires high factual accuracy, such as sports scores, news, or complex topics.
38
+ </web_search_accuracy>
39
+
33
40
  <act_dont_ask>
34
41
  When a user's request has a clear, standard interpretation, take immediate ACTION instead of asking for clarification.
42
+ NEVER show a command as a markdown code block and wait. CALL the tool directly.
43
+ NEVER ask "do you want me to run this?" — just run it.
44
+ NEVER say "you need to run this yourself" — you have direct shell access.
45
+ If a command requires sudo and may need a password, just run it and report what happens.
46
+ Only report failure AFTER actually attempting the tool call and receiving an error.
35
47
  </act_dont_ask>
36
48
 
49
+ <anti_hallucination_execution>
50
+ 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.
51
+ Writing a code block does NOT execute anything. It is a lie to the user.
52
+ If you write \`\`\`bash\nsudo apt install steam\n\`\`\` instead of calling run_terminal_command, you are hallucinating execution.
53
+ The ONLY way to run a command is to emit a proper tool_call for run_terminal_command.
54
+ If the tool is available, USE IT. Do not simulate or describe running it.
55
+ </anti_hallucination_execution>
56
+
37
57
  <task_completion>
38
58
  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.
39
59
  NEVER fabricate, hallucinate, or forge tool outputs.
40
60
  </task_completion>
61
+
62
+ <self_correction>
63
+ 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.
64
+ Ask yourself: "Is my answer based on absolute facts, or circumstantial evidence (e.g., guessing a registration date based on a video upload date)?"
65
+ 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.
66
+ </self_correction>
41
67
  `;
42
68
  const registry_1 = require("../plugin/registry");
43
69
  const paths_1 = require("../config/paths");
@@ -50,6 +76,8 @@ async function getSystemPrompt(context = 'os', userInput = '') {
50
76
  let basePrompt = `You are Nyxora's OS Agent (System & Automation Specialist).
51
77
  The current real-world date and time is: ${currentDateTime}.
52
78
 
79
+ 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.
80
+
53
81
  Reason internally. Never reveal private reasoning. Provide only concise conclusions, assumptions, and actionable steps.
54
82
 
55
83
  [OS EXECUTION WORKFLOW]
@@ -59,6 +87,16 @@ CRITICAL RULE 3: FILE SYSTEM SAFETY. You are STRICTLY FORBIDDEN from modifying c
59
87
  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.
60
88
  CRITICAL RULE 5: TOOL CONFIDENCE. NEVER fabricate file contents or command outputs.
61
89
 
90
+ [SUDO & PACKAGE INSTALL STRATEGY]
91
+ This tool runs in a NON-INTERACTIVE shell (no TTY). Therefore:
92
+ - ALWAYS prefix apt/apt-get commands with: DEBIAN_FRONTEND=noninteractive
93
+ - ALWAYS use the -y flag for package installations to auto-confirm.
94
+ - If a command fails with "sudo: a password is required" or similar, DO NOT promise to retry without actually retrying. Instead:
95
+ 1. First retry with: echo 'USER_PASSWORD' | sudo -S <command> — but since you don't know the password, skip this.
96
+ 2. Instead, try running the command WITHOUT sudo if possible (e.g. for user-space tools).
97
+ 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.
98
+ - NEVER promise to retry ("let me try again", "I'll run it again", etc.) without immediately making another tool_call in the same response.
99
+
62
100
  ${EXECUTION_DISCIPLINE}
63
101
  `;
64
102
  // Inject Active Cognitive Skills
@@ -68,7 +106,7 @@ ${EXECUTION_DISCIPLINE}
68
106
  }
69
107
  // Inject Episodic Memories via Python RAG
70
108
  try {
71
- const ragRes = await fetch('http://localhost:8000/memory/rag', {
109
+ const ragRes = await fetch('http://127.0.0.1:8000/memory/rag', {
72
110
  method: 'POST',
73
111
  headers: { 'Content-Type': 'application/json' },
74
112
  body: JSON.stringify({ query: userInput, top_k: 5 })
@@ -118,6 +156,30 @@ async function processOsIntent(input, role = 'user', onProgress, sessionId) {
118
156
  const config = (0, parser_1.loadConfig)();
119
157
  // Add input to memory
120
158
  exports.logger.addEntry({ role, content: input }, sessionId);
159
+ // --- MULTILINGUAL USER CORRECTION DETECTION ---
160
+ const correctionSignals = [
161
+ // ID
162
+ 'salah', 'ngawur', 'keliru', 'bukan', 'tidak benar', 'perbaiki', 'coba lagi',
163
+ // EN
164
+ 'wrong', 'incorrect', 'mistake', 'not right', 'false', 'bad', 'fix', 'try again',
165
+ // ES & FR
166
+ 'incorrecto', 'mal', 'equivocado', 'error', 'falso', 'faux', 'erreur', 'mauvais',
167
+ // DE & RU
168
+ 'falsch', 'inkorrekt', 'fehler', 'ошибка', 'неправильно', 'неверно',
169
+ // JP & ZH
170
+ '違う', '間違い', 'やり直して', '错误', '不对'
171
+ ];
172
+ if (role === 'user' && correctionSignals.some(s => input.toLowerCase().includes(s))) {
173
+ exports.logger.addEntry({
174
+ role: 'system',
175
+ content: `[USER CORRECTION DETECTED] The user indicated your previous answer was WRONG, STALE, or INACCURATE.
176
+ CRITICAL INSTRUCTIONS:
177
+ 1. Do NOT just apologize and repeat the same data from your memory.
178
+ 2. The data in your training memory or previous tool calls is likely stale/incorrect.
179
+ 3. You MUST call a tool (like search_web or others) NOW to verify the facts with FRESH data.
180
+ 4. Base your new answer strictly on the NEW tool results.`
181
+ }, sessionId);
182
+ }
121
183
  let activeTools = [...registry_1.pluginManager.getAllToolDefinitions()];
122
184
  activeTools = activeTools.filter(t => (0, skillManager_1.isSkillActive)(t.function.name));
123
185
  // P1: Init reasoning scratchpad for this request
@@ -129,6 +191,7 @@ async function processOsIntent(input, role = 'user', onProgress, sessionId) {
129
191
  let turnCount = 0;
130
192
  const MAX_TURNS = 10;
131
193
  let consecutiveToolErrors = 0;
194
+ let criticHasFired = false; // Critic Pass hanya aktif 1x per request
132
195
  while (turnCount < MAX_TURNS) {
133
196
  turnCount++;
134
197
  const currentHistory = exports.logger.getHistory(sessionId);
@@ -169,6 +232,36 @@ async function processOsIntent(input, role = 'user', onProgress, sessionId) {
169
232
  tool_calls: responseMessage.tool_calls,
170
233
  }, sessionId);
171
234
  if (!responseMessage.tool_calls || responseMessage.tool_calls.length === 0) {
235
+ // --- CRITIC PASS (Self-Improvement) ---
236
+ const isLongResponse = (cleanedContent?.length ?? 0) > 100;
237
+ if (isLongResponse && !criticHasFired) {
238
+ criticHasFired = true;
239
+ try {
240
+ const criticRes = await fetch('http://127.0.0.1:8000/cognitive/critic', {
241
+ method: 'POST',
242
+ headers: { 'Content-Type': 'application/json' },
243
+ body: JSON.stringify({ user_input: input, draft_answer: cleanedContent, current_utc_datetime: new Date().toISOString() })
244
+ });
245
+ if (criticRes.ok) {
246
+ const evaluation = await criticRes.json();
247
+ if (evaluation.needs_revision) {
248
+ console.log(picocolors_1.default.cyan(`[🧠 Critic] Revision needed. Confidence: ${evaluation.factual_confidence?.toFixed(2)}. Completeness: ${evaluation.completeness?.toFixed(2)}. Re-generating...`));
249
+ exports.logger.addEntry({
250
+ role: 'system',
251
+ 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.`
252
+ }, sessionId);
253
+ continue; // Loop kembali ke Generator untuk revisi
254
+ }
255
+ else {
256
+ console.log(picocolors_1.default.green(`[🧠 Critic] Passed. Confidence: ${evaluation.factual_confidence?.toFixed(2)}, Completeness: ${evaluation.completeness?.toFixed(2)}.`));
257
+ }
258
+ }
259
+ }
260
+ catch {
261
+ // Python ML Engine tidak aktif — skip Critic, lanjut seperti biasa
262
+ }
263
+ }
264
+ // --- END CRITIC PASS ---
172
265
  return cleanedContent || 'No response generated.';
173
266
  }
174
267
  let canFastReturnAll = true;
@@ -291,6 +384,30 @@ async function processOsIntent(input, role = 'user', onProgress, sessionId) {
291
384
  async function processOsIntentStream(input, onChunk, onProgress, sessionId) {
292
385
  const config = (0, parser_1.loadConfig)();
293
386
  exports.logger.addEntry({ role: 'user', content: input }, sessionId);
387
+ // --- MULTILINGUAL USER CORRECTION DETECTION ---
388
+ const correctionSignals = [
389
+ // ID
390
+ 'salah', 'ngawur', 'keliru', 'bukan', 'tidak benar', 'perbaiki', 'coba lagi',
391
+ // EN
392
+ 'wrong', 'incorrect', 'mistake', 'not right', 'false', 'bad', 'fix', 'try again',
393
+ // ES & FR
394
+ 'incorrecto', 'mal', 'equivocado', 'error', 'falso', 'faux', 'erreur', 'mauvais',
395
+ // DE & RU
396
+ 'falsch', 'inkorrekt', 'fehler', 'ошибка', 'неправильно', 'неверно',
397
+ // JP & ZH
398
+ '違う', '間違い', 'やり直して', '错误', '不对'
399
+ ];
400
+ if (correctionSignals.some(s => input.toLowerCase().includes(s))) {
401
+ exports.logger.addEntry({
402
+ role: 'system',
403
+ content: `[USER CORRECTION DETECTED] The user indicated your previous answer was WRONG, STALE, or INACCURATE.
404
+ CRITICAL INSTRUCTIONS:
405
+ 1. Do NOT just apologize and repeat the same data from your memory.
406
+ 2. The data in your training memory or previous tool calls is likely stale/incorrect.
407
+ 3. You MUST call a tool (like search_web or others) NOW to verify the facts with FRESH data.
408
+ 4. Base your new answer strictly on the NEW tool results.`
409
+ }, sessionId);
410
+ }
294
411
  const pluginTools = registry_1.pluginManager.getAllToolDefinitions();
295
412
  let activeTools = [...pluginTools].filter(t => (0, skillManager_1.isSkillActive)(t.function.name));
296
413
  const { sanitizeHistoryForLLM } = require('../utils/historySanitizer');
@@ -298,6 +415,7 @@ async function processOsIntentStream(input, onChunk, onProgress, sessionId) {
298
415
  let turnCount = 0;
299
416
  const MAX_TURNS = 10;
300
417
  let fullResponse = '';
418
+ let criticHasFiredStream = false; // Critic Pass hanya aktif 1x per request
301
419
  while (turnCount < MAX_TURNS) {
302
420
  turnCount++;
303
421
  const currentHistory = exports.logger.getHistory(sessionId);
@@ -327,6 +445,36 @@ async function processOsIntentStream(input, onChunk, onProgress, sessionId) {
327
445
  if (!responseMessage.tool_calls || responseMessage.tool_calls.length === 0) {
328
446
  let finalContent = responseMessage.content || 'No response generated.';
329
447
  finalContent = finalContent.replace(/<(think|thought|thinking|reasoning|analysis|reflection)[\s\S]*?<\/\1>\n?/gi, '').trim();
448
+ // --- CRITIC PASS (Self-Improvement) ---
449
+ const isLongResponseStream = finalContent.length > 100;
450
+ if (isLongResponseStream && !criticHasFiredStream) {
451
+ criticHasFiredStream = true;
452
+ try {
453
+ const criticRes = await fetch('http://127.0.0.1:8000/cognitive/critic', {
454
+ method: 'POST',
455
+ headers: { 'Content-Type': 'application/json' },
456
+ body: JSON.stringify({ user_input: input, draft_answer: finalContent, current_utc_datetime: new Date().toISOString() })
457
+ });
458
+ if (criticRes.ok) {
459
+ const evaluation = await criticRes.json();
460
+ if (evaluation.needs_revision) {
461
+ console.log(picocolors_1.default.cyan(`[🧠 Critic] Revision needed. Confidence: ${evaluation.factual_confidence?.toFixed(2)}. Completeness: ${evaluation.completeness?.toFixed(2)}. Re-generating...`));
462
+ exports.logger.addEntry({
463
+ role: 'system',
464
+ 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.`
465
+ }, sessionId);
466
+ continue; // Loop kembali ke Generator untuk revisi
467
+ }
468
+ else {
469
+ console.log(picocolors_1.default.green(`[🧠 Critic] Passed. Confidence: ${evaluation.factual_confidence?.toFixed(2)}, Completeness: ${evaluation.completeness?.toFixed(2)}.`));
470
+ }
471
+ }
472
+ }
473
+ catch {
474
+ // Python ML Engine tidak aktif — skip Critic, lanjut seperti biasa
475
+ }
476
+ }
477
+ // --- END CRITIC PASS ---
330
478
  fullResponse = finalContent;
331
479
  break;
332
480
  }
@@ -126,7 +126,7 @@ Do NOT perform any web3 tasks or generic answers until they provide all 4 detail
126
126
  }
127
127
  // Inject Episodic Memories via Python RAG
128
128
  try {
129
- const ragRes = await fetch('http://localhost:8000/memory/rag', {
129
+ const ragRes = await fetch('http://127.0.0.1:8000/memory/rag', {
130
130
  method: 'POST',
131
131
  headers: { 'Content-Type': 'application/json' },
132
132
  body: JSON.stringify({ query: userInput, top_k: 5 })
@@ -78,7 +78,7 @@ ${EXECUTION_DISCIPLINE}
78
78
  `;
79
79
  // Inject Episodic Memories via Python RAG
80
80
  try {
81
- const ragRes = await fetch('http://localhost:8000/memory/rag', {
81
+ const ragRes = await fetch('http://127.0.0.1:8000/memory/rag', {
82
82
  method: 'POST',
83
83
  headers: { 'Content-Type': 'application/json' },
84
84
  body: JSON.stringify({ query: userInput, top_k: 5 })
@@ -15,7 +15,9 @@ const CREDENTIALS_PATH = (0, paths_1.getPath)('google-credentials.json');
15
15
  const FALLBACK_TOKEN_PATH = (0, paths_1.getPath)('google-tokens.json');
16
16
  const SCOPES = [
17
17
  'https://www.googleapis.com/auth/gmail.readonly',
18
+ 'https://www.googleapis.com/auth/gmail.send',
18
19
  'https://www.googleapis.com/auth/calendar.readonly',
20
+ 'https://www.googleapis.com/auth/calendar.events',
19
21
  'https://www.googleapis.com/auth/documents.readonly',
20
22
  'https://www.googleapis.com/auth/spreadsheets',
21
23
  'https://www.googleapis.com/auth/forms.responses.readonly',
@@ -145,7 +145,7 @@ function startTelegramBot() {
145
145
  if (!isDrafting && now - lastDraftAt >= THROTTLE_MS) {
146
146
  isDrafting = true;
147
147
  try {
148
- await ctx.api.sendMessageDraft(ctx.chat.id, draft_id, formatToTelegramHTML(buffer), { parse_mode: 'HTML' });
148
+ await ctx.api.sendMessageDraft(ctx.chat.id, draft_id, formatToTelegramHTML(buffer), { parse_mode: 'HTML', reply_parameters: { message_id: ctx.message.message_id } });
149
149
  }
150
150
  catch { }
151
151
  lastDraftAt = Date.now();
@@ -156,7 +156,7 @@ function startTelegramBot() {
156
156
  if (!isDrafting) {
157
157
  isDrafting = true;
158
158
  try {
159
- await ctx.api.sendMessageDraft(ctx.chat.id, draft_id, `<i>${msg.replace(/_/g, '')}</i>`, { parse_mode: 'HTML' });
159
+ await ctx.api.sendMessageDraft(ctx.chat.id, draft_id, `<i>${msg.replace(/_/g, '')}</i>`, { parse_mode: 'HTML', reply_parameters: { message_id: ctx.message.message_id } });
160
160
  }
161
161
  catch { }
162
162
  isDrafting = false;
@@ -171,13 +171,13 @@ function startTelegramBot() {
171
171
  .text('✅ Approve', 'tx_approve')
172
172
  .text('❌ Reject', 'tx_reject');
173
173
  }
174
- await ctx.reply(formatToTelegramHTML(response), { parse_mode: 'HTML', reply_markup: replyMarkup }).catch((e) => {
174
+ await ctx.reply(formatToTelegramHTML(response), { parse_mode: 'HTML', reply_markup: replyMarkup, reply_parameters: { message_id: ctx.message.message_id } }).catch((e) => {
175
175
  console.error("[Telegram] CRITICAL: ctx.reply failed in text handler:", e.message);
176
176
  });
177
177
  }
178
178
  catch (error) {
179
179
  console.error('[Telegram] Error processing message:', error);
180
- await ctx.reply('❌ Sorry, I encountered an error while processing your message.', {}).catch(() => { });
180
+ await ctx.reply('❌ Sorry, I encountered an error while processing your message.', { reply_parameters: { message_id: ctx.message.message_id } }).catch(() => { });
181
181
  }
182
182
  });
183
183
  bot.on('callback_query:data', async (ctx) => {
@@ -253,7 +253,7 @@ function startTelegramBot() {
253
253
  const onProgress = async (progressText) => {
254
254
  try {
255
255
  if (!progressMsgId) {
256
- const sent = await ctx.reply(`<i>${progressText.replace(/_/g, '')}</i>`, { parse_mode: 'HTML' });
256
+ const sent = await ctx.reply(`<i>${progressText.replace(/_/g, '')}</i>`, { parse_mode: 'HTML', reply_parameters: { message_id: ctx.message?.message_id } });
257
257
  progressMsgId = sent.message_id;
258
258
  }
259
259
  else {
@@ -266,11 +266,11 @@ function startTelegramBot() {
266
266
  if (progressMsgId) {
267
267
  await ctx.api.deleteMessage(ctx.chat.id, progressMsgId).catch(() => { });
268
268
  }
269
- await ctx.reply(formatToTelegramHTML(response), { parse_mode: 'HTML' });
269
+ await ctx.reply(formatToTelegramHTML(response), { parse_mode: 'HTML', reply_parameters: { message_id: ctx.message?.message_id } });
270
270
  }
271
271
  catch (error) {
272
272
  console.error('[Telegram] Error processing document:', error);
273
- await ctx.reply('❌ Sorry, I failed to download or analyze the document.');
273
+ await ctx.reply('❌ Sorry, I failed to download or analyze the document.', { reply_parameters: { message_id: ctx.message?.message_id } });
274
274
  }
275
275
  });
276
276
  // Transaction approval and rejection are now handled conversationally via the LLM and the confirm_pending_tx tool.
@@ -136,12 +136,23 @@ class EpisodicMemoryDB {
136
136
  if (!value || !value.trim())
137
137
  return;
138
138
  const existing = this.db.prepare('SELECT id, confidence FROM user_personas WHERE category = ?').get(category);
139
- if (existing) {
140
- const newConfidence = Math.min(1.0, existing.confidence + (confidence * 0.2));
141
- this.db.prepare('UPDATE user_personas SET trait = ?, confidence = ?, source = ?, lastUpdated = CURRENT_TIMESTAMP WHERE id = ?').run(value.trim(), newConfidence, source, existing.id);
139
+ try {
140
+ if (existing) {
141
+ const newConfidence = Math.min(1.0, existing.confidence + (confidence * 0.2));
142
+ this.db.prepare('UPDATE user_personas SET trait = ?, confidence = ?, source = ?, lastUpdated = CURRENT_TIMESTAMP WHERE id = ?').run(value.trim(), newConfidence, source, existing.id);
143
+ }
144
+ else {
145
+ this.db.prepare('INSERT INTO user_personas (trait, category, confidence, source) VALUES (?, ?, ?, ?)').run(value.trim(), category, confidence, source);
146
+ }
142
147
  }
143
- else {
144
- this.db.prepare('INSERT INTO user_personas (trait, category, confidence, source) VALUES (?, ?, ?, ?)').run(value.trim(), category, confidence, source);
148
+ catch (e) {
149
+ // Handle UNIQUE constraint collision if the trait already exists in the database (e.g. from an older version with category 'general')
150
+ if (e.message && e.message.includes('UNIQUE constraint failed')) {
151
+ this.db.prepare('UPDATE user_personas SET category = ?, confidence = ?, source = ?, lastUpdated = CURRENT_TIMESTAMP WHERE trait = ?').run(category, confidence, source, value.trim());
152
+ }
153
+ else {
154
+ throw e;
155
+ }
145
156
  }
146
157
  }
147
158
  getPersonas() {
@@ -11,7 +11,9 @@ class GoogleWorkspacePlugin {
11
11
  googleWorkspace_1.listCalendarEventsToolDefinition,
12
12
  googleWorkspace_1.appendRowToSheetsToolDefinition,
13
13
  googleWorkspace_1.readGoogleDocsToolDefinition,
14
- googleWorkspace_1.readGoogleFormResponsesToolDefinition
14
+ googleWorkspace_1.readGoogleFormResponsesToolDefinition,
15
+ googleWorkspace_1.sendEmailToolDefinition,
16
+ googleWorkspace_1.addCalendarEventToolDefinition
15
17
  ];
16
18
  handlers = {
17
19
  ['read_gmail_inbox']: async (args) => {
@@ -28,6 +30,12 @@ class GoogleWorkspacePlugin {
28
30
  },
29
31
  ['read_google_form_responses']: async (args) => {
30
32
  return await (0, googleWorkspace_1.readGoogleFormResponses)(args.formId);
33
+ },
34
+ ['send_email']: async (args) => {
35
+ return await (0, googleWorkspace_1.sendEmail)(args.to, args.subject, args.body);
36
+ },
37
+ ['add_calendar_event']: async (args) => {
38
+ return await (0, googleWorkspace_1.addCalendarEvent)(args.summary, args.description, args.startTime, args.endTime);
31
39
  }
32
40
  };
33
41
  }
@@ -3,18 +3,45 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runTerminalCommandToolDefinition = void 0;
4
4
  exports.runTerminalCommand = runTerminalCommand;
5
5
  const child_process_1 = require("child_process");
6
+ const parser_1 = require("../../config/parser");
6
7
  function runTerminalCommand(command) {
7
8
  return new Promise((resolve) => {
8
- (0, child_process_1.exec)(command, { maxBuffer: 1024 * 1024 * 10 }, (error, stdout, stderr) => {
9
+ // --- SUDO AUTO-INJECTION ---
10
+ // If command requires sudo, inject password from config via sudo -S
11
+ let finalCommand = command;
12
+ const needsSudo = /^\s*sudo\s/.test(command);
13
+ if (needsSudo) {
14
+ try {
15
+ const config = (0, parser_1.loadConfig)();
16
+ const sudoPassword = config.security?.sudo_password;
17
+ if (sudoPassword) {
18
+ // Inject password via stdin using echo | sudo -S
19
+ const escaped = sudoPassword.replace(/'/g, "'\\''");
20
+ finalCommand = command.replace(/^\s*sudo\s/, `echo '${escaped}' | sudo -S `);
21
+ }
22
+ }
23
+ catch (e) {
24
+ // config load failed, proceed without password injection
25
+ }
26
+ }
27
+ (0, child_process_1.exec)(finalCommand, { maxBuffer: 1024 * 1024 * 10 }, (error, stdout, stderr) => {
9
28
  let output = "";
10
29
  if (stdout)
11
30
  output += `STDOUT:\n${stdout}\n`;
12
- if (stderr)
13
- output += `STDERR:\n${stderr}\n`;
31
+ // Filter out the sudo password prompt noise from stderr
32
+ const filteredStderr = stderr
33
+ ? stderr.split('\n').filter(l => !l.includes('[sudo] password for')).join('\n').trim()
34
+ : '';
35
+ if (filteredStderr)
36
+ output += `STDERR:\n${filteredStderr}\n`;
14
37
  if (error)
15
38
  output += `ERROR:\n${error.message}\n`;
16
39
  if (!output)
17
40
  output = "Command executed successfully with no output.";
41
+ // If sudo failed due to missing password, give a helpful hint
42
+ if (needsSudo && (output.includes('sudo: a password is required') || output.includes('no password supplied'))) {
43
+ 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}`;
44
+ }
18
45
  // --- OUTPUT REDACTION LAYER ---
19
46
  // 1. Secret Exfiltration Redaction (Keys, Mnemonics, UUIDs, EVM/Solana Keys)
20
47
  const secretPatterns = [
@@ -52,7 +79,7 @@ exports.runTerminalCommandToolDefinition = {
52
79
  type: "function",
53
80
  function: {
54
81
  name: "run_terminal_command",
55
- description: "Executes a shell/terminal command on the user's host machine. Use this to install packages, run scripts, manage processes, etc.",
82
+ 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.",
56
83
  parameters: {
57
84
  type: "object",
58
85
  properties: {
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readGoogleFormResponsesToolDefinition = exports.readGoogleDocsToolDefinition = exports.appendRowToSheetsToolDefinition = exports.listCalendarEventsToolDefinition = exports.readGmailInboxToolDefinition = void 0;
3
+ exports.addCalendarEventToolDefinition = exports.sendEmailToolDefinition = exports.readGoogleFormResponsesToolDefinition = exports.readGoogleDocsToolDefinition = exports.appendRowToSheetsToolDefinition = exports.listCalendarEventsToolDefinition = exports.readGmailInboxToolDefinition = void 0;
4
4
  exports.readGmailInbox = readGmailInbox;
5
5
  exports.listCalendarEvents = listCalendarEvents;
6
6
  exports.appendRowToSheets = appendRowToSheets;
7
7
  exports.readGoogleDocs = readGoogleDocs;
8
8
  exports.readGoogleFormResponses = readGoogleFormResponses;
9
+ exports.sendEmail = sendEmail;
10
+ exports.addCalendarEvent = addCalendarEvent;
9
11
  const googleAuthModule_1 = require("../../gateway/googleAuthModule");
10
12
  async function readGmailInbox(maxResults = 5) {
11
13
  const isAuth = await (0, googleAuthModule_1.isAuthenticated)();
@@ -240,3 +242,106 @@ exports.readGoogleFormResponsesToolDefinition = {
240
242
  },
241
243
  },
242
244
  };
245
+ async function sendEmail(to, subject, body) {
246
+ const isAuth = await (0, googleAuthModule_1.isAuthenticated)();
247
+ if (!isAuth)
248
+ return "Google Auth not configured. Please link your Google account in the dashboard.";
249
+ const token = await (0, googleAuthModule_1.getAccessToken)();
250
+ if (!token)
251
+ return "Failed to retrieve access token.";
252
+ try {
253
+ const messageParts = [
254
+ `To: ${to}`,
255
+ `Subject: ${subject}`,
256
+ `Content-Type: text/plain; charset=utf-8`,
257
+ '',
258
+ body
259
+ ];
260
+ const message = messageParts.join('\n');
261
+ const encodedMessage = Buffer.from(message).toString('base64')
262
+ .replace(/\+/g, '-')
263
+ .replace(/\//g, '_')
264
+ .replace(/=+$/, '');
265
+ const res = await fetch(`https://gmail.googleapis.com/gmail/v1/users/me/messages/send`, {
266
+ method: 'POST',
267
+ headers: {
268
+ Authorization: `Bearer ${token}`,
269
+ 'Content-Type': 'application/json'
270
+ },
271
+ body: JSON.stringify({ raw: encodedMessage })
272
+ });
273
+ const data = await res.json();
274
+ if (!res.ok) {
275
+ return `Failed to send email: ${data.error?.message || JSON.stringify(data)}`;
276
+ }
277
+ return `Successfully sent email to ${to} with Message ID: ${data.id}`;
278
+ }
279
+ catch (err) {
280
+ return `Error sending email: ${err.message}`;
281
+ }
282
+ }
283
+ async function addCalendarEvent(summary, description, startTime, endTime) {
284
+ const isAuth = await (0, googleAuthModule_1.isAuthenticated)();
285
+ if (!isAuth)
286
+ return "Google Auth not configured. Please link your Google account in the dashboard.";
287
+ const token = await (0, googleAuthModule_1.getAccessToken)();
288
+ if (!token)
289
+ return "Failed to retrieve access token.";
290
+ try {
291
+ const event = {
292
+ summary,
293
+ description,
294
+ start: { dateTime: startTime },
295
+ end: { dateTime: endTime }
296
+ };
297
+ const res = await fetch(`https://www.googleapis.com/calendar/v3/calendars/primary/events`, {
298
+ method: 'POST',
299
+ headers: {
300
+ Authorization: `Bearer ${token}`,
301
+ 'Content-Type': 'application/json'
302
+ },
303
+ body: JSON.stringify(event)
304
+ });
305
+ const data = await res.json();
306
+ if (!res.ok) {
307
+ return `Failed to add calendar event: ${data.error?.message || JSON.stringify(data)}`;
308
+ }
309
+ return `Successfully added event '${summary}'. Event link: ${data.htmlLink}`;
310
+ }
311
+ catch (err) {
312
+ return `Error adding calendar event: ${err.message}`;
313
+ }
314
+ }
315
+ exports.sendEmailToolDefinition = {
316
+ type: "function",
317
+ function: {
318
+ name: "send_email",
319
+ description: "Sends an email using the user's Gmail account.",
320
+ parameters: {
321
+ type: "object",
322
+ properties: {
323
+ to: { type: "string", description: "The recipient's email address." },
324
+ subject: { type: "string", description: "The subject of the email." },
325
+ body: { type: "string", description: "The plain text body of the email." }
326
+ },
327
+ required: ["to", "subject", "body"],
328
+ },
329
+ },
330
+ };
331
+ exports.addCalendarEventToolDefinition = {
332
+ type: "function",
333
+ function: {
334
+ name: "add_calendar_event",
335
+ description: "Adds a new event to the user's primary Google Calendar.",
336
+ parameters: {
337
+ type: "object",
338
+ properties: {
339
+ summary: { type: "string", description: "The title or summary of the event." },
340
+ description: { type: "string", description: "A description of the event." },
341
+ startTime: { type: "string", description: "Start time of the event in ISO 8601 format (e.g. 2026-07-03T09:00:00+07:00)." },
342
+ endTime: { type: "string", description: "End time of the event in ISO 8601 format (e.g. 2026-07-03T10:00:00+07:00)." }
343
+ },
344
+ required: ["summary", "description", "startTime", "endTime"],
345
+ },
346
+ },
347
+ };