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.
- package/CHANGELOG.md +18 -1
- package/README.md +2 -2
- package/dist/packages/core/src/agent/llmProvider.js +4 -1
- package/dist/packages/core/src/agent/nyxDaemon.js +1 -1
- package/dist/packages/core/src/agent/osAgent.js +149 -1
- package/dist/packages/core/src/agent/reasoning.js +1 -1
- package/dist/packages/core/src/agent/web3Agent.js +1 -1
- package/dist/packages/core/src/gateway/googleAuthModule.js +2 -0
- package/dist/packages/core/src/gateway/telegram.js +7 -7
- package/dist/packages/core/src/memory/episodic.js +16 -5
- package/dist/packages/core/src/system/plugins/GoogleWorkspacePlugin.js +9 -1
- package/dist/packages/core/src/system/skills/executeShell.js +31 -4
- package/dist/packages/core/src/system/skills/googleWorkspace.js +106 -1
- package/dist/packages/core/src/system/skills/searchWeb.js +13 -6
- package/dist/packages/core/src/web3/skills/getPrice.js +1 -1
- package/dist/packages/core/src/web3/skills/marketAnalysis.js +1 -1
- package/package.json +3 -2
- package/packages/core/package.json +1 -1
- package/packages/core/src/agent/llmProvider.ts +4 -2
- package/packages/core/src/agent/nyxDaemon.ts +1 -1
- package/packages/core/src/agent/osAgent.ts +152 -1
- package/packages/core/src/agent/reasoning.ts +1 -1
- package/packages/core/src/agent/web3Agent.ts +1 -1
- package/packages/core/src/gateway/googleAuthModule.ts +2 -0
- package/packages/core/src/gateway/telegram.ts +7 -7
- package/packages/core/src/memory/episodic.ts +21 -9
- package/packages/core/src/system/plugins/GoogleWorkspacePlugin.ts +14 -2
- package/packages/core/src/system/skills/executeShell.ts +38 -7
- package/packages/core/src/system/skills/googleWorkspace.ts +109 -0
- package/packages/core/src/system/skills/searchWeb.ts +12 -6
- package/packages/core/src/web3/skills/getPrice.ts +1 -1
- package/packages/core/src/web3/skills/marketAnalysis.ts +1 -1
- package/packages/dashboard/dist/assets/{index-BTfp141V.js → index-Czxksiao.js} +1 -1
- package/packages/dashboard/dist/index.html +1 -1
- package/packages/dashboard/package.json +1 -1
- package/packages/mcp-server/package.json +1 -1
- package/packages/ml-engine/__pycache__/config.cpython-313.pyc +0 -0
- package/packages/ml-engine/__pycache__/main.cpython-313.pyc +0 -0
- package/packages/ml-engine/config.py +59 -0
- package/packages/ml-engine/main.py +34 -0
- package/packages/ml-engine/requirements.txt +22 -0
- package/packages/ml-engine/routers/__init__.py +1 -0
- package/packages/ml-engine/routers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/packages/ml-engine/routers/__pycache__/cognitive.cpython-313.pyc +0 -0
- package/packages/ml-engine/routers/__pycache__/critic.cpython-313.pyc +0 -0
- package/packages/ml-engine/routers/__pycache__/llm.cpython-313.pyc +0 -0
- package/packages/ml-engine/routers/__pycache__/market.cpython-313.pyc +0 -0
- package/packages/ml-engine/routers/__pycache__/memory.cpython-313.pyc +0 -0
- package/packages/ml-engine/routers/cognitive.py +98 -0
- package/packages/ml-engine/routers/critic.py +111 -0
- package/packages/ml-engine/routers/llm.py +38 -0
- package/packages/ml-engine/routers/market.py +332 -0
- package/packages/ml-engine/routers/memory.py +125 -0
- package/packages/policy/package.json +1 -1
- 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 =
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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://
|
|
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://
|
|
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
|
+
"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"
|
|
@@ -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
|
-
|
|
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://
|
|
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://
|
|
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://
|
|
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://
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
|