natureco-cli 5.6.48 → 5.7.1
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 +100 -0
- package/DEPLOY_v2.0.0.md +0 -0
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/README.md.bak +0 -0
- package/bin/natureco.js +0 -0
- package/package.json +14 -6
- package/skills/code-review/SKILL.md +0 -0
- package/skills/summarize/SKILL.md +0 -0
- package/skills/translate/SKILL.md +0 -0
- package/src/commands/acp.js +0 -0
- package/src/commands/admin-rpc.js +0 -0
- package/src/commands/agent.js +0 -0
- package/src/commands/agents.js +0 -0
- package/src/commands/approvals.js +0 -0
- package/src/commands/ask.js +0 -0
- package/src/commands/backup.js +0 -0
- package/src/commands/bonjour.js +0 -0
- package/src/commands/bots.js +0 -0
- package/src/commands/browser.js +0 -0
- package/src/commands/capability.js +0 -0
- package/src/commands/channels.js +0 -0
- package/src/commands/chat.js +5 -0
- package/src/commands/clawbot.js +0 -0
- package/src/commands/clickclack.js +0 -0
- package/src/commands/code.js +0 -0
- package/src/commands/commands.js +0 -0
- package/src/commands/commitments.js +0 -0
- package/src/commands/completion.js +0 -0
- package/src/commands/config.js +4 -0
- package/src/commands/configure.js +0 -0
- package/src/commands/crestodian.js +0 -0
- package/src/commands/cron.js +0 -0
- package/src/commands/daemon.js +0 -0
- package/src/commands/dashboard.js +0 -0
- package/src/commands/device-pair.js +0 -0
- package/src/commands/devices.js +0 -0
- package/src/commands/directory.js +0 -0
- package/src/commands/discord.js +0 -0
- package/src/commands/dns.js +0 -0
- package/src/commands/docs.js +0 -0
- package/src/commands/doctor.js +0 -0
- package/src/commands/exec-policy.js +0 -0
- package/src/commands/gateway-server.js +38 -3
- package/src/commands/gateway.js +2 -0
- package/src/commands/git.js +0 -0
- package/src/commands/health.js +0 -0
- package/src/commands/help.js +0 -0
- package/src/commands/hooks.js +0 -0
- package/src/commands/imessage.js +0 -0
- package/src/commands/infer.js +0 -0
- package/src/commands/init.js +0 -0
- package/src/commands/irc.js +0 -0
- package/src/commands/login.js +0 -0
- package/src/commands/logout.js +0 -0
- package/src/commands/logs.js +0 -0
- package/src/commands/mattermost.js +0 -0
- package/src/commands/mcp.js +0 -0
- package/src/commands/memory-cmd.js +0 -0
- package/src/commands/memory.js +0 -0
- package/src/commands/message.js +0 -0
- package/src/commands/migrate.js +0 -0
- package/src/commands/models.js +0 -0
- package/src/commands/node.js +0 -0
- package/src/commands/nodes.js +4 -0
- package/src/commands/oc-path.js +0 -0
- package/src/commands/onboard.js +0 -0
- package/src/commands/open-prose.js +0 -0
- package/src/commands/pairing.js +0 -0
- package/src/commands/path.js +0 -0
- package/src/commands/plugins.js +0 -0
- package/src/commands/policy.js +0 -0
- package/src/commands/proxy.js +0 -0
- package/src/commands/qr.js +0 -0
- package/src/commands/repl.js +13 -18
- package/src/commands/reset.js +0 -0
- package/src/commands/run.js +0 -0
- package/src/commands/sandbox.js +0 -0
- package/src/commands/secrets.js +0 -0
- package/src/commands/security.js +0 -0
- package/src/commands/sessions.js +0 -0
- package/src/commands/setup.js +7 -6
- package/src/commands/signal.js +0 -0
- package/src/commands/skills.js +0 -0
- package/src/commands/slack.js +0 -0
- package/src/commands/sms.js +0 -0
- package/src/commands/status.js +0 -0
- package/src/commands/system.js +0 -0
- package/src/commands/tasks.js +0 -0
- package/src/commands/telegram.js +0 -0
- package/src/commands/terminal.js +0 -0
- package/src/commands/thread-ownership.js +0 -0
- package/src/commands/transcripts.js +0 -0
- package/src/commands/tui.js +0 -0
- package/src/commands/ultrareview.js +0 -0
- package/src/commands/uninstall.js +0 -0
- package/src/commands/update.js +0 -0
- package/src/commands/voice.js +0 -0
- package/src/commands/vydra.js +0 -0
- package/src/commands/web-fetch.js +0 -0
- package/src/commands/webhooks.js +0 -0
- package/src/commands/whatsapp.js +0 -0
- package/src/commands/wiki.js +0 -0
- package/src/commands/workboard.js +0 -0
- package/src/tools/audio_understanding.js +0 -0
- package/src/tools/bash.js +0 -0
- package/src/tools/browser.js +0 -0
- package/src/tools/canvas.js +0 -0
- package/src/tools/dashboard.js +2 -1
- package/src/tools/document_extract.js +0 -0
- package/src/tools/duckduckgo.js +0 -0
- package/src/tools/exa_search.js +0 -0
- package/src/tools/filesystem.js +0 -0
- package/src/tools/firecrawl.js +0 -0
- package/src/tools/git.js +0 -0
- package/src/tools/http.js +0 -0
- package/src/tools/image_generation.js +0 -0
- package/src/tools/list_dir.js +0 -0
- package/src/tools/llm_task.js +0 -0
- package/src/tools/media_understanding.js +0 -0
- package/src/tools/memory_write.js +70 -16
- package/src/tools/music_generation.js +0 -0
- package/src/tools/parallel_search.js +0 -0
- package/src/tools/phone_control.js +0 -0
- package/src/tools/phone_control_enhanced.js +0 -0
- package/src/tools/read_file.js +0 -0
- package/src/tools/searxng.js +0 -0
- package/src/tools/speech_to_text.js +0 -0
- package/src/tools/text_to_speech.js +0 -0
- package/src/tools/thread_ownership.js +0 -0
- package/src/tools/video_generation.js +0 -0
- package/src/tools/web_readability.js +0 -0
- package/src/tools/web_search.js +0 -0
- package/src/tools/write_file.js +0 -0
- package/src/utils/agents-md.js +0 -0
- package/src/utils/agents.js +0 -0
- package/src/utils/api.js +47 -40
- package/src/utils/approvals.js +27 -12
- package/src/utils/atomic-file.js +91 -0
- package/src/utils/background.js +0 -0
- package/src/utils/baileys.js +0 -0
- package/src/utils/commands.js +0 -0
- package/src/utils/config.js +0 -0
- package/src/utils/cron.js +0 -0
- package/src/utils/dashboard-server.js +3 -2
- package/src/utils/error.js +4 -0
- package/src/utils/errors.js +0 -0
- package/src/utils/format.js +0 -0
- package/src/utils/gateway-ws.js +0 -0
- package/src/utils/headless.js +0 -0
- package/src/utils/history.js +6 -14
- package/src/utils/hooks.js +0 -0
- package/src/utils/inquirer-wrapper.js +0 -0
- package/src/utils/mcp-client.js +0 -0
- package/src/utils/mcp.js +0 -0
- package/src/utils/memory.js +0 -0
- package/src/utils/parallel-tools.js +0 -0
- package/src/utils/path-utils.js +0 -0
- package/src/utils/plugin-registry.js +0 -0
- package/src/utils/ports.js +44 -0
- package/src/utils/process-errors.js +115 -0
- package/src/utils/provider-detect.js +78 -0
- package/src/utils/secrets.js +0 -0
- package/src/utils/sessions.js +6 -13
- package/src/utils/skills.js +0 -0
- package/src/utils/streaming-tools.js +97 -0
- package/src/utils/sub-agent.js +0 -0
- package/src/utils/token-budget.js +0 -0
- package/src/utils/tool-adapter.js +0 -0
- package/src/utils/tool-runner.js +0 -0
- package/src/utils/tools.js +4 -2
- package/src/utils/web-fetch.js +0 -0
|
File without changes
|
|
File without changes
|
package/src/commands/update.js
CHANGED
|
File without changes
|
package/src/commands/voice.js
CHANGED
|
File without changes
|
package/src/commands/vydra.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/commands/webhooks.js
CHANGED
|
File without changes
|
package/src/commands/whatsapp.js
CHANGED
|
File without changes
|
package/src/commands/wiki.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/tools/bash.js
CHANGED
|
File without changes
|
package/src/tools/browser.js
CHANGED
|
File without changes
|
package/src/tools/canvas.js
CHANGED
|
File without changes
|
package/src/tools/dashboard.js
CHANGED
|
@@ -15,8 +15,9 @@ const http = require("http");
|
|
|
15
15
|
const fs = require("fs");
|
|
16
16
|
const path = require("path");
|
|
17
17
|
const os = require("os");
|
|
18
|
+
const { getDashboardPort } = require("../utils/ports");
|
|
18
19
|
|
|
19
|
-
const PORT =
|
|
20
|
+
const PORT = getDashboardPort();
|
|
20
21
|
const DASHBOARD_HTML = `
|
|
21
22
|
<!DOCTYPE html>
|
|
22
23
|
<html lang="tr">
|
|
File without changes
|
package/src/tools/duckduckgo.js
CHANGED
|
File without changes
|
package/src/tools/exa_search.js
CHANGED
|
File without changes
|
package/src/tools/filesystem.js
CHANGED
|
File without changes
|
package/src/tools/firecrawl.js
CHANGED
|
File without changes
|
package/src/tools/git.js
CHANGED
|
File without changes
|
package/src/tools/http.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/tools/list_dir.js
CHANGED
|
File without changes
|
package/src/tools/llm_task.js
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -8,53 +8,93 @@
|
|
|
8
8
|
const fs = require("fs");
|
|
9
9
|
const path = require("path");
|
|
10
10
|
const os = require("os");
|
|
11
|
+
const { writeJsonAtomicSync, readJsonSafeSync } = require("../utils/atomic-file");
|
|
11
12
|
|
|
12
13
|
const MEMORY_DIR = path.join(os.homedir(), ".natureco", "memory");
|
|
13
14
|
|
|
15
|
+
// Soft cap on per-user fact count. Decay already prunes old/low-importance
|
|
16
|
+
// facts; this just bounds the worst-case file size and prompt-injection
|
|
17
|
+
// surface. Configurable via NATURECO_MAX_FACTS (default 50, was a hard 15
|
|
18
|
+
// that was silently truncating new writes when full).
|
|
19
|
+
const MAX_FACTS_PER_USER = (() => {
|
|
20
|
+
const raw = parseInt(process.env.NATURECO_MAX_FACTS || "", 10);
|
|
21
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 50;
|
|
22
|
+
})();
|
|
23
|
+
|
|
14
24
|
function getMemoryFile(username) {
|
|
15
25
|
const name = (username || "default").toLowerCase();
|
|
16
26
|
return path.join(MEMORY_DIR, `${name}.json`);
|
|
17
27
|
}
|
|
18
28
|
|
|
29
|
+
function _emptyMemory(username) {
|
|
30
|
+
return { name: username || "User", nickname: null, botName: null, facts: [], preferences: [] };
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
function loadMemory(username) {
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
if (!fs.existsSync(file)) {
|
|
23
|
-
return { name: username || "User", nickname: null, botName: null, facts: [], preferences: [] };
|
|
24
|
-
}
|
|
25
|
-
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
26
|
-
} catch {
|
|
27
|
-
return { name: username || "User", nickname: null, botName: null, facts: [], preferences: [] };
|
|
28
|
-
}
|
|
34
|
+
return readJsonSafeSync(getMemoryFile(username), _emptyMemory(username));
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
function saveMemory(username, memory) {
|
|
32
38
|
if (!fs.existsSync(MEMORY_DIR)) fs.mkdirSync(MEMORY_DIR, { recursive: true });
|
|
33
39
|
memory.lastUpdated = new Date().toISOString();
|
|
34
|
-
|
|
40
|
+
writeJsonAtomicSync(getMemoryFile(username), memory);
|
|
35
41
|
return memory;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
/**
|
|
39
|
-
* Score azalt (eski fact'ler zamanla
|
|
45
|
+
* Score azalt (eski fact'ler zamanla unutulur).
|
|
46
|
+
* Sıralama veya soft-cap UYGULAMAZ — onu enforceFactLimit yapar
|
|
47
|
+
* (push'tan sonra çağrılır, böylece yeni fact silinmez).
|
|
40
48
|
*/
|
|
41
49
|
function decayFacts(memory) {
|
|
42
50
|
if (!memory.facts) return memory;
|
|
43
51
|
const now = Date.now();
|
|
44
52
|
memory.facts = memory.facts.map(f => {
|
|
45
53
|
if (!f.score) f.score = 5;
|
|
46
|
-
// 1 haftadan eski -1, 1 aydan eski -3
|
|
47
54
|
const ageMs = now - new Date(f.updatedAt || f.createdAt || now).getTime();
|
|
48
55
|
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
49
56
|
if (ageDays > 30) f.score = Math.max(0, f.score - 3);
|
|
50
57
|
else if (ageDays > 7) f.score = Math.max(0, f.score - 1);
|
|
51
58
|
return f;
|
|
52
59
|
});
|
|
53
|
-
// score 0 olanlari sil
|
|
54
60
|
memory.facts = memory.facts.filter(f => (f.score || 0) > 0);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
return memory;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Soft cap: yeni fact eklenmesinden SONRA uygulanır, böylece az önce
|
|
66
|
+
* yazılan fact eviction kurbanı olmaz. En düşük score + en eski updatedAt
|
|
67
|
+
* önce gider. Cap aşılırsa stderr'e tek satır warn yazar (eski "silent
|
|
68
|
+
* truncate to 15" davranışını gözlemlenebilir hale getirir).
|
|
69
|
+
*
|
|
70
|
+
* @param {{facts: Array<object>}} memory
|
|
71
|
+
* @param {{recentValue?: string}} [opts] Korunması zorunlu fact (yeni eklenen)
|
|
72
|
+
* @returns the same memory
|
|
73
|
+
*/
|
|
74
|
+
function enforceFactLimit(memory, opts = {}) {
|
|
75
|
+
if (!memory.facts || memory.facts.length <= MAX_FACTS_PER_USER) return memory;
|
|
76
|
+
const before = memory.facts.length;
|
|
77
|
+
const recent = opts.recentValue ? opts.recentValue.toLowerCase() : null;
|
|
78
|
+
// Sort by score desc, then updatedAt desc (newest+highest first).
|
|
79
|
+
// The just-pushed fact is pinned at the top regardless of its score.
|
|
80
|
+
memory.facts.sort((a, b) => {
|
|
81
|
+
if (recent) {
|
|
82
|
+
if ((a.value || "").toLowerCase() === recent) return -1;
|
|
83
|
+
if ((b.value || "").toLowerCase() === recent) return 1;
|
|
84
|
+
}
|
|
85
|
+
const sa = a.score || 0, sb = b.score || 0;
|
|
86
|
+
if (sa !== sb) return sb - sa;
|
|
87
|
+
return String(b.updatedAt || "").localeCompare(String(a.updatedAt || ""));
|
|
88
|
+
});
|
|
89
|
+
memory.facts = memory.facts.slice(0, MAX_FACTS_PER_USER);
|
|
90
|
+
const dropped = before - memory.facts.length;
|
|
91
|
+
if (dropped > 0 && !process.env.NATURECO_QUIET_MEMORY) {
|
|
92
|
+
// eslint-disable-next-line no-console
|
|
93
|
+
console.warn(
|
|
94
|
+
`[memory] cap ${MAX_FACTS_PER_USER} aşıldı, en düşük skorlu ${dropped} fact düşürüldü ` +
|
|
95
|
+
`(NATURECO_MAX_FACTS ile değiştir, NATURECO_QUIET_MEMORY=1 ile sustur)`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
58
98
|
return memory;
|
|
59
99
|
}
|
|
60
100
|
|
|
@@ -122,6 +162,8 @@ function addMemory({ username, fact, score = 5, category = "general", botName, n
|
|
|
122
162
|
createdAt: new Date().toISOString(),
|
|
123
163
|
});
|
|
124
164
|
}
|
|
165
|
+
// Limit'i ZIM push'tan sonra uygula → yeni eklenen fact düşürülmez.
|
|
166
|
+
memory = enforceFactLimit(memory, { recentValue: fact });
|
|
125
167
|
}
|
|
126
168
|
|
|
127
169
|
if (!memory.preferences) memory.preferences = [];
|
|
@@ -173,6 +215,18 @@ function showMemory({ username }) {
|
|
|
173
215
|
}
|
|
174
216
|
|
|
175
217
|
module.exports = {
|
|
218
|
+
// Exposed for tests / advanced consumers — not part of the tool schema.
|
|
219
|
+
_internals: {
|
|
220
|
+
MAX_FACTS_PER_USER,
|
|
221
|
+
enforceFactLimit,
|
|
222
|
+
decayFacts,
|
|
223
|
+
loadMemory,
|
|
224
|
+
saveMemory,
|
|
225
|
+
addMemory,
|
|
226
|
+
clearMemory,
|
|
227
|
+
showMemory,
|
|
228
|
+
getMemoryFile,
|
|
229
|
+
},
|
|
176
230
|
name: "memory_write",
|
|
177
231
|
description: "Memory'ye yeni fact/bilgi kaydet veya bot ismini/nickname'i degistir. Kalici, REPL'in extractMemoryFromMessage ozelligi.",
|
|
178
232
|
inputSchema: {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/tools/read_file.js
CHANGED
|
File without changes
|
package/src/tools/searxng.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/tools/web_search.js
CHANGED
|
File without changes
|
package/src/tools/write_file.js
CHANGED
|
File without changes
|
package/src/utils/agents-md.js
CHANGED
|
File without changes
|
package/src/utils/agents.js
CHANGED
|
File without changes
|
package/src/utils/api.js
CHANGED
|
@@ -9,26 +9,17 @@ const { getConfig } = require('./config');
|
|
|
9
9
|
const { getToolDefinitions, executeToolCalls } = require('./tool-runner');
|
|
10
10
|
const { MCPClient } = require('./mcp-client');
|
|
11
11
|
const TB = require('./token-budget');
|
|
12
|
+
const { accumulateToolCallDeltas } = require('./streaming-tools');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* v5.5.0: Provider-specific format detection
|
|
15
16
|
* Groq, OpenAI, Anthropic, Mistral, DeepSeek, OpenRouter, Ollama, MiniMax
|
|
17
|
+
*
|
|
18
|
+
* Canonical implementation lives in src/utils/provider-detect.js;
|
|
19
|
+
* re-exported here so the historical `detectProvider` reference inside
|
|
20
|
+
* api.js continues to work without touching every call site.
|
|
16
21
|
*/
|
|
17
|
-
|
|
18
|
-
const url = (providerUrl || '').toLowerCase();
|
|
19
|
-
const m = (model || '').toLowerCase();
|
|
20
|
-
if (url.includes('anthropic.com') || m.includes('claude')) return 'anthropic';
|
|
21
|
-
if (url.includes('groq.com') || m.includes('groq') || m.includes('llama-3') || m.includes('mixtral')) return 'groq';
|
|
22
|
-
if (url.includes('openrouter.ai')) return 'openrouter';
|
|
23
|
-
if (url.includes('api.deepseek.com') || m.includes('deepseek')) return 'deepseek';
|
|
24
|
-
if (url.includes('mistral.ai') || m.includes('mistral') || m.includes('codestral')) return 'mistral';
|
|
25
|
-
if (url.includes('together.xyz') || m.includes('together')) return 'together';
|
|
26
|
-
if (url.includes('fireworks.ai') || m.includes('fireworks')) return 'fireworks';
|
|
27
|
-
if (url.includes('perplexity.ai') || m.includes('pplx') || m.includes('sonar')) return 'perplexity';
|
|
28
|
-
if (url.includes('localhost') || url.includes('127.0.0.1') || url.includes('ollama')) return 'ollama';
|
|
29
|
-
if (url.includes('minimax.io') || url.includes('minimax')) return 'minimax';
|
|
30
|
-
return 'openai'; // default
|
|
31
|
-
}
|
|
22
|
+
const { detectProvider, isMiniMax } = require('./provider-detect');
|
|
32
23
|
|
|
33
24
|
/**
|
|
34
25
|
* v5.5.0: Tool definitions'ı provider'a göre normalize et
|
|
@@ -81,6 +72,29 @@ function parseToolCallsFromResponse(message, provider) {
|
|
|
81
72
|
return message.tool_calls || [];
|
|
82
73
|
}
|
|
83
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Anthropic Messages API requires `system` to be either a non-empty string
|
|
77
|
+
* or omitted entirely. Sending `undefined` (which JSON.stringify drops to
|
|
78
|
+
* silent absence) leaves the model unanchored; sending `''` returns 400
|
|
79
|
+
* "system: cannot be empty" on recent API revisions. Always pass a
|
|
80
|
+
* meaningful default when no system message is present.
|
|
81
|
+
*/
|
|
82
|
+
const DEFAULT_ANTHROPIC_SYSTEM =
|
|
83
|
+
'You are a helpful AI assistant running inside the natureco CLI.';
|
|
84
|
+
|
|
85
|
+
function extractSystemForAnthropic(messages) {
|
|
86
|
+
const systemMsg = messages.find(m => m.role === 'system');
|
|
87
|
+
if (!systemMsg) return DEFAULT_ANTHROPIC_SYSTEM;
|
|
88
|
+
// content may be a string or an array of content blocks; both round-trip.
|
|
89
|
+
if (typeof systemMsg.content === 'string') {
|
|
90
|
+
return systemMsg.content.trim() || DEFAULT_ANTHROPIC_SYSTEM;
|
|
91
|
+
}
|
|
92
|
+
if (Array.isArray(systemMsg.content) && systemMsg.content.length > 0) {
|
|
93
|
+
return systemMsg.content;
|
|
94
|
+
}
|
|
95
|
+
return DEFAULT_ANTHROPIC_SYSTEM;
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
/**
|
|
85
99
|
* v5.5.0: System mesajı provider'a göre ayarla
|
|
86
100
|
* - OpenAI: messages[].role=system
|
|
@@ -88,8 +102,6 @@ function parseToolCallsFromResponse(message, provider) {
|
|
|
88
102
|
*/
|
|
89
103
|
function buildRequestBody(messages, model, options, provider) {
|
|
90
104
|
if (provider === 'anthropic') {
|
|
91
|
-
// System mesajını ayır
|
|
92
|
-
const systemMsg = messages.find(m => m.role === 'system');
|
|
93
105
|
const userMsgs = messages.filter(m => m.role !== 'system');
|
|
94
106
|
return {
|
|
95
107
|
model,
|
|
@@ -97,7 +109,7 @@ function buildRequestBody(messages, model, options, provider) {
|
|
|
97
109
|
role: m.role,
|
|
98
110
|
content: m.content
|
|
99
111
|
})),
|
|
100
|
-
system:
|
|
112
|
+
system: extractSystemForAnthropic(messages),
|
|
101
113
|
max_tokens: options.max_tokens || 4096,
|
|
102
114
|
temperature: options.temperature || 0.7,
|
|
103
115
|
...(options.tools && options.tools.length > 0 ? { tools: options.tools } : {})
|
|
@@ -534,9 +546,8 @@ function formatToolsForAnthropic() {
|
|
|
534
546
|
*/
|
|
535
547
|
async function sendMessageOpenAICompatible(providerConfig, messages, tools) {
|
|
536
548
|
const baseUrl = providerConfig.url.replace(/\/+$/, '');
|
|
537
|
-
// MiniMax özel endpoint tespiti
|
|
538
|
-
const
|
|
539
|
-
const endpoint = isMiniMax
|
|
549
|
+
// MiniMax özel endpoint tespiti — provider-detect.js'deki kanonik tanım.
|
|
550
|
+
const endpoint = isMiniMax(baseUrl)
|
|
540
551
|
? `${baseUrl}/v1/text/chatcompletion_v2`
|
|
541
552
|
: `${baseUrl}/chat/completions`;
|
|
542
553
|
const requestBody = {
|
|
@@ -587,11 +598,10 @@ async function sendMessageOpenAICompatible(providerConfig, messages, tools) {
|
|
|
587
598
|
*/
|
|
588
599
|
async function sendMessageAnthropic(providerConfig, messages, tools) {
|
|
589
600
|
const endpoint = `${providerConfig.url}/v1/messages`;
|
|
590
|
-
|
|
591
|
-
// Anthropic requires system message separate
|
|
592
|
-
const systemMessage = messages.find(m => m.role === 'system');
|
|
601
|
+
|
|
602
|
+
// Anthropic requires system message separate; never send empty string.
|
|
593
603
|
const userMessages = messages.filter(m => m.role !== 'system');
|
|
594
|
-
|
|
604
|
+
|
|
595
605
|
const response = await fetch(endpoint, {
|
|
596
606
|
method: 'POST',
|
|
597
607
|
headers: {
|
|
@@ -602,7 +612,7 @@ async function sendMessageAnthropic(providerConfig, messages, tools) {
|
|
|
602
612
|
body: JSON.stringify({
|
|
603
613
|
model: providerConfig.model,
|
|
604
614
|
max_tokens: 2000,
|
|
605
|
-
system:
|
|
615
|
+
system: extractSystemForAnthropic(messages),
|
|
606
616
|
messages: userMessages,
|
|
607
617
|
tools: tools,
|
|
608
618
|
}),
|
|
@@ -992,9 +1002,8 @@ async function streamProviderCompletion(providerConfig, messages, tools) {
|
|
|
992
1002
|
|
|
993
1003
|
async function streamOpenAICompletion(providerConfig, messages, tools) {
|
|
994
1004
|
const baseUrl = providerConfig.url.replace(/\/+$/, '');
|
|
995
|
-
// MiniMax özel endpoint tespiti (streaming için de aynı)
|
|
996
|
-
const
|
|
997
|
-
const endpoint = isMiniMax
|
|
1005
|
+
// MiniMax özel endpoint tespiti (streaming için de aynı) — provider-detect.js.
|
|
1006
|
+
const endpoint = isMiniMax(baseUrl)
|
|
998
1007
|
? `${baseUrl}/v1/text/chatcompletion_v2`
|
|
999
1008
|
: `${baseUrl}/chat/completions`;
|
|
1000
1009
|
|
|
@@ -1046,15 +1055,7 @@ async function streamOpenAICompletion(providerConfig, messages, tools) {
|
|
|
1046
1055
|
|
|
1047
1056
|
if (delta.tool_calls) {
|
|
1048
1057
|
hasToolCalls = true;
|
|
1049
|
-
|
|
1050
|
-
const idx = tc.index;
|
|
1051
|
-
if (!toolCalls[idx]) {
|
|
1052
|
-
toolCalls[idx] = { id: tc.id || '', type: 'function', function: { name: '', arguments: '' } };
|
|
1053
|
-
}
|
|
1054
|
-
if (tc.id) toolCalls[idx].id = tc.id;
|
|
1055
|
-
if (tc.function?.name) toolCalls[idx].function.name += tc.function.name;
|
|
1056
|
-
if (tc.function?.arguments) toolCalls[idx].function.arguments += tc.function.arguments;
|
|
1057
|
-
}
|
|
1058
|
+
accumulateToolCallDeltas(toolCalls, delta.tool_calls);
|
|
1058
1059
|
}
|
|
1059
1060
|
|
|
1060
1061
|
const token = delta.content || '';
|
|
@@ -1089,7 +1090,6 @@ async function streamOpenAICompletion(providerConfig, messages, tools) {
|
|
|
1089
1090
|
async function streamAnthropicCompletion(providerConfig, messages) {
|
|
1090
1091
|
const endpoint = `${providerConfig.url}/v1/messages`;
|
|
1091
1092
|
|
|
1092
|
-
const systemMessage = messages.find(m => m.role === 'system');
|
|
1093
1093
|
const userMessages = messages.filter(m => m.role !== 'system');
|
|
1094
1094
|
|
|
1095
1095
|
const response = await fetch(endpoint, {
|
|
@@ -1102,7 +1102,7 @@ async function streamAnthropicCompletion(providerConfig, messages) {
|
|
|
1102
1102
|
body: JSON.stringify({
|
|
1103
1103
|
model: providerConfig.model,
|
|
1104
1104
|
max_tokens: 2000,
|
|
1105
|
-
system:
|
|
1105
|
+
system: extractSystemForAnthropic(messages),
|
|
1106
1106
|
messages: userMessages,
|
|
1107
1107
|
stream: true,
|
|
1108
1108
|
}),
|
|
@@ -1154,5 +1154,12 @@ module.exports = {
|
|
|
1154
1154
|
streamProviderCompletion,
|
|
1155
1155
|
streamOpenAICompletion,
|
|
1156
1156
|
streamAnthropicCompletion,
|
|
1157
|
+
// Exposed for tests + advanced consumers (does not appear in the
|
|
1158
|
+
// public API surface of natureco's user-facing docs).
|
|
1159
|
+
_internals: {
|
|
1160
|
+
extractSystemForAnthropic,
|
|
1161
|
+
DEFAULT_ANTHROPIC_SYSTEM,
|
|
1162
|
+
buildRequestBody,
|
|
1163
|
+
},
|
|
1157
1164
|
_sendMessage: sendMessage,
|
|
1158
1165
|
};
|
package/src/utils/approvals.js
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
-
const net = require('net');
|
|
5
4
|
const chalk = require('chalk');
|
|
6
5
|
const inquirer = require('./inquirer-wrapper');
|
|
7
6
|
const { NatureCoError } = require('./errors');
|
|
7
|
+
const { writeJsonAtomicSync, readJsonSafeSync } = require('./atomic-file');
|
|
8
8
|
|
|
9
9
|
const APPROVALS_FILE = path.join(os.homedir(), '.natureco', 'exec-approvals.json');
|
|
10
|
-
const APPROVALS_SOCKET_PATH = path.join(os.homedir(), '.natureco', 'exec-approvals.sock');
|
|
11
10
|
const DEFAULT_TIMEOUT_MS = 1800000; // 30 min
|
|
12
11
|
|
|
12
|
+
// 0o600 = owner read/write only. The allowlist controls which commands
|
|
13
|
+
// auto-execute without prompting; on a shared machine, world-read leaks
|
|
14
|
+
// the user's automation surface and world-write lets another local
|
|
15
|
+
// account inject auto-approved commands. Treat it like ssh keys.
|
|
16
|
+
const APPROVALS_FILE_MODE = 0o600;
|
|
17
|
+
const APPROVALS_DIR_MODE = 0o700;
|
|
18
|
+
|
|
19
|
+
function _emptyApprovals() {
|
|
20
|
+
return { version: 1, defaults: { security: 'full', ask: 'off' }, agents: {} };
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
class ExecApprovalError extends NatureCoError {
|
|
14
24
|
constructor(message, options = {}) {
|
|
15
25
|
super(message, options);
|
|
@@ -33,20 +43,22 @@ function getApprovalsPath() {
|
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
function loadApprovals() {
|
|
36
|
-
|
|
37
|
-
return { version: 1, defaults: { security: 'full', ask: 'off' }, agents: {} };
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
return JSON.parse(fs.readFileSync(APPROVALS_FILE, 'utf8'));
|
|
41
|
-
} catch {
|
|
42
|
-
return { version: 1, defaults: { security: 'full', ask: 'off' }, agents: {} };
|
|
43
|
-
}
|
|
46
|
+
return readJsonSafeSync(APPROVALS_FILE, _emptyApprovals());
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
function saveApprovals(data) {
|
|
47
50
|
const dir = path.dirname(APPROVALS_FILE);
|
|
48
|
-
if (!fs.existsSync(dir))
|
|
49
|
-
|
|
51
|
+
if (!fs.existsSync(dir)) {
|
|
52
|
+
fs.mkdirSync(dir, { recursive: true, mode: APPROVALS_DIR_MODE });
|
|
53
|
+
} else {
|
|
54
|
+
// Tighten the dir even if it pre-existed with looser bits.
|
|
55
|
+
try { fs.chmodSync(dir, APPROVALS_DIR_MODE); } catch { /* best-effort */ }
|
|
56
|
+
}
|
|
57
|
+
writeJsonAtomicSync(APPROVALS_FILE, data, { mode: APPROVALS_FILE_MODE });
|
|
58
|
+
// Tighten the file even if it pre-existed with looser bits (the rename
|
|
59
|
+
// in writeJsonAtomicSync preserves the temp's mode, but only when we
|
|
60
|
+
// pass it through — defensively chmod again in case of older installs).
|
|
61
|
+
try { fs.chmodSync(APPROVALS_FILE, APPROVALS_FILE_MODE); } catch { /* best-effort */ }
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
function resolveEffectivePolicy(agentId) {
|
|
@@ -294,4 +306,7 @@ module.exports = {
|
|
|
294
306
|
getApprovalsPath,
|
|
295
307
|
DANGEROUS_PATTERNS,
|
|
296
308
|
SAFE_COMMANDS,
|
|
309
|
+
APPROVALS_FILE,
|
|
310
|
+
APPROVALS_FILE_MODE,
|
|
311
|
+
APPROVALS_DIR_MODE,
|
|
297
312
|
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crash-safe file write + safe read.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: `fs.writeFileSync(path, data)` is NOT atomic. If the
|
|
5
|
+
* process is killed mid-write (SIGTERM, OOM, power loss) the file is
|
|
6
|
+
* left truncated, which becomes a "corrupted session" on next load
|
|
7
|
+
* (JSON.parse throws and session history is lost).
|
|
8
|
+
*
|
|
9
|
+
* `writeFileAtomicSync(path, data)` writes to a temp sibling first, then
|
|
10
|
+
* `rename(2)`s it into place — atomic on POSIX when both paths are on
|
|
11
|
+
* the same filesystem (always true here, since the temp is in the same
|
|
12
|
+
* directory).
|
|
13
|
+
*
|
|
14
|
+
* `readFileSafeSync(path, fallback)` returns the parsed JSON or falls
|
|
15
|
+
* back gracefully on missing/corrupted files instead of throwing.
|
|
16
|
+
*/
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
|
|
21
|
+
function _tempName(target) {
|
|
22
|
+
// Same directory so rename(2) is atomic. Include pid+rand to avoid
|
|
23
|
+
// collisions if two processes write the same file.
|
|
24
|
+
const dir = path.dirname(target);
|
|
25
|
+
const base = path.basename(target);
|
|
26
|
+
const suffix = `${process.pid}.${crypto.randomBytes(4).toString('hex')}.tmp`;
|
|
27
|
+
return path.join(dir, `.${base}.${suffix}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Atomically write a string/Buffer to `filePath`.
|
|
32
|
+
* On error the temp file is cleaned up and the original is untouched.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} filePath
|
|
35
|
+
* @param {string|Buffer} data
|
|
36
|
+
* @param {{encoding?: BufferEncoding, mode?: number}} [opts]
|
|
37
|
+
*/
|
|
38
|
+
function writeFileAtomicSync(filePath, data, opts = {}) {
|
|
39
|
+
const encoding = opts.encoding ?? 'utf-8';
|
|
40
|
+
const mode = opts.mode; // undefined → fs default
|
|
41
|
+
const tmp = _tempName(filePath);
|
|
42
|
+
try {
|
|
43
|
+
if (mode !== undefined) {
|
|
44
|
+
fs.writeFileSync(tmp, data, { encoding, mode });
|
|
45
|
+
} else {
|
|
46
|
+
fs.writeFileSync(tmp, data, encoding);
|
|
47
|
+
}
|
|
48
|
+
fs.renameSync(tmp, filePath);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
// Best-effort cleanup; if the temp already vanished, ignore.
|
|
51
|
+
try { fs.unlinkSync(tmp); } catch { /* ignore */ }
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Read `filePath` as JSON; return `fallback` on missing or corrupt.
|
|
58
|
+
*
|
|
59
|
+
* @template T
|
|
60
|
+
* @param {string} filePath
|
|
61
|
+
* @param {T} fallback
|
|
62
|
+
* @returns {T | object | Array<any>}
|
|
63
|
+
*/
|
|
64
|
+
function readJsonSafeSync(filePath, fallback) {
|
|
65
|
+
if (!fs.existsSync(filePath)) return fallback;
|
|
66
|
+
try {
|
|
67
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
68
|
+
if (!raw.trim()) return fallback;
|
|
69
|
+
return JSON.parse(raw);
|
|
70
|
+
} catch {
|
|
71
|
+
return fallback;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Convenience: stringify + atomic write. Pretty-printed for human-edit
|
|
77
|
+
* friendliness (matches the existing JSON.stringify(..., null, 2) calls).
|
|
78
|
+
*
|
|
79
|
+
* @param {string} filePath
|
|
80
|
+
* @param {any} value
|
|
81
|
+
* @param {{mode?: number}} [opts] e.g. {mode: 0o600} for secrets/PII
|
|
82
|
+
*/
|
|
83
|
+
function writeJsonAtomicSync(filePath, value, opts = {}) {
|
|
84
|
+
writeFileAtomicSync(filePath, JSON.stringify(value, null, 2), opts);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
writeFileAtomicSync,
|
|
89
|
+
writeJsonAtomicSync,
|
|
90
|
+
readJsonSafeSync,
|
|
91
|
+
};
|
package/src/utils/background.js
CHANGED
|
File without changes
|
package/src/utils/baileys.js
CHANGED
|
File without changes
|
package/src/utils/commands.js
CHANGED
|
File without changes
|
package/src/utils/config.js
CHANGED
|
File without changes
|
package/src/utils/cron.js
CHANGED
|
File without changes
|
|
@@ -18,9 +18,10 @@ const fs = require('fs');
|
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const os = require('os');
|
|
20
20
|
const url = require('url');
|
|
21
|
+
const { getDashboardPort, getDashboardHost } = require('./ports');
|
|
21
22
|
|
|
22
|
-
const PORT =
|
|
23
|
-
const HOST =
|
|
23
|
+
const PORT = getDashboardPort();
|
|
24
|
+
const HOST = getDashboardHost();
|
|
24
25
|
|
|
25
26
|
const NATURECO_DIR = path.join(os.homedir(), '.natureco');
|
|
26
27
|
|
package/src/utils/error.js
CHANGED
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* - Error codes
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
// checkMacPermission below uses os.platform() — was relying on a global
|
|
13
|
+
// `os` from another module's load order.
|
|
14
|
+
const os = require('os');
|
|
15
|
+
|
|
12
16
|
class ToolError extends Error {
|
|
13
17
|
constructor(message, code = "TOOL_ERROR", details = {}) {
|
|
14
18
|
super(message);
|
package/src/utils/errors.js
CHANGED
|
File without changes
|
package/src/utils/format.js
CHANGED
|
File without changes
|
package/src/utils/gateway-ws.js
CHANGED
|
File without changes
|
package/src/utils/headless.js
CHANGED
|
File without changes
|