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
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
|
-
|
|
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
|
-
[](#)
|
|
6
|
+
|
|
7
7
|
[](#)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
[](#️-advanced-security-threat-model)
|
|
@@ -220,7 +220,10 @@ class AnthropicAdapter {
|
|
|
220
220
|
mergedAnthropic.push(m);
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
|
-
|
|
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://
|
|
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://
|
|
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://
|
|
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://
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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
|
|
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
|
+
};
|