alvin-bot 5.6.2 → 5.8.0
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 +29 -0
- package/README.md +1 -1
- package/dist/claude.js +1 -102
- package/dist/config.js +1 -96
- package/dist/engine.js +1 -90
- package/dist/find-claude-binary.js +1 -98
- package/dist/handlers/async-agent-chunk-handler.js +1 -50
- package/dist/handlers/background-bypass.js +1 -75
- package/dist/handlers/commands.js +1 -2336
- package/dist/handlers/cron-progress.js +1 -52
- package/dist/handlers/document.js +1 -194
- package/dist/handlers/message.js +1 -959
- package/dist/handlers/photo.js +1 -154
- package/dist/handlers/platform-message.js +1 -360
- package/dist/handlers/stuck-timer.js +1 -54
- package/dist/handlers/video.js +1 -237
- package/dist/handlers/voice.js +1 -148
- package/dist/i18n.js +1 -805
- package/dist/index.js +1 -697
- package/dist/init-data-dir.js +1 -98
- package/dist/middleware/auth.js +1 -233
- package/dist/migrate.js +1 -162
- package/dist/paths.js +1 -146
- package/dist/platforms/discord.js +1 -175
- package/dist/platforms/index.js +1 -130
- package/dist/platforms/signal.js +1 -205
- package/dist/platforms/slack-slash-parser.js +1 -32
- package/dist/platforms/slack.js +1 -501
- package/dist/platforms/telegram.js +1 -111
- package/dist/platforms/types.js +1 -8
- package/dist/platforms/whatsapp-auth-helpers.js +1 -53
- package/dist/platforms/whatsapp.js +1 -707
- package/dist/providers/claude-sdk-provider.js +1 -565
- package/dist/providers/codex-cli-provider.js +1 -134
- package/dist/providers/index.js +1 -7
- package/dist/providers/ollama-provider.js +1 -32
- package/dist/providers/openai-compatible.js +1 -406
- package/dist/providers/registry.js +1 -352
- package/dist/providers/runtime-header.js +1 -45
- package/dist/providers/tool-executor.js +1 -475
- package/dist/providers/types.js +1 -227
- package/dist/services/access.js +1 -144
- package/dist/services/allowed-users-gate.js +1 -56
- package/dist/services/alvin-dispatch.js +1 -130
- package/dist/services/alvin-mcp-tools.js +1 -104
- package/dist/services/asset-index.js +1 -224
- package/dist/services/async-agent-parser.js +1 -418
- package/dist/services/async-agent-watcher.js +1 -443
- package/dist/services/auto-diagnostic.js +1 -228
- package/dist/services/broadcast.js +1 -52
- package/dist/services/browser-manager.js +1 -562
- package/dist/services/browser-webfetch.js +1 -127
- package/dist/services/browser.js +1 -121
- package/dist/services/cdp-bootstrap.js +1 -357
- package/dist/services/compaction.js +1 -144
- package/dist/services/critical-notify.js +1 -203
- package/dist/services/cron-resolver.js +1 -58
- package/dist/services/cron-scheduling.js +1 -310
- package/dist/services/cron.js +1 -861
- package/dist/services/custom-tools.js +1 -317
- package/dist/services/delivery-queue.js +1 -173
- package/dist/services/delivery-registry.js +1 -21
- package/dist/services/disk-cleanup.js +1 -203
- package/dist/services/elevenlabs.js +1 -58
- package/dist/services/embeddings/auto-detect.js +1 -74
- package/dist/services/embeddings/fts5.js +1 -108
- package/dist/services/embeddings/gemini.js +1 -65
- package/dist/services/embeddings/index.js +1 -496
- package/dist/services/embeddings/ollama.js +1 -78
- package/dist/services/embeddings/openai.js +1 -49
- package/dist/services/embeddings/provider.js +1 -22
- package/dist/services/embeddings/vector-base.js +1 -113
- package/dist/services/embeddings-migration.js +1 -193
- package/dist/services/embeddings.js +1 -9
- package/dist/services/env-file.js +1 -50
- package/dist/services/exec-guard.js +1 -71
- package/dist/services/fallback-order.js +1 -154
- package/dist/services/file-permissions.js +1 -93
- package/dist/services/heartbeat-file.js +1 -65
- package/dist/services/heartbeat.js +1 -313
- package/dist/services/hooks.js +1 -44
- package/dist/services/imagegen.js +1 -72
- package/dist/services/language-detect.js +1 -154
- package/dist/services/markdown.js +1 -63
- package/dist/services/mcp.js +1 -263
- package/dist/services/memory-extractor.js +1 -178
- package/dist/services/memory-inject-mode.js +1 -43
- package/dist/services/memory-layers.js +1 -156
- package/dist/services/memory.js +1 -146
- package/dist/services/ollama-manager.js +1 -339
- package/dist/services/permissions-wizard.js +1 -291
- package/dist/services/personality.js +1 -376
- package/dist/services/plugins.js +1 -171
- package/dist/services/preflight.js +1 -292
- package/dist/services/process-manager.js +1 -291
- package/dist/services/release-highlights.js +1 -79
- package/dist/services/reminders.js +1 -97
- package/dist/services/restart.js +1 -48
- package/dist/services/security-audit.js +1 -74
- package/dist/services/self-diagnosis.js +1 -272
- package/dist/services/self-search.js +1 -129
- package/dist/services/session-persistence.js +1 -237
- package/dist/services/session.js +1 -282
- package/dist/services/skills.js +1 -290
- package/dist/services/ssrf-guard.js +1 -162
- package/dist/services/standing-orders.js +1 -29
- package/dist/services/steer-channel.js +1 -46
- package/dist/services/stop-controller.js +1 -52
- package/dist/services/subagent-dedup.js +1 -0
- package/dist/services/subagent-delivery.js +1 -452
- package/dist/services/subagent-stats.js +1 -123
- package/dist/services/subagents.js +1 -814
- package/dist/services/sudo.js +1 -329
- package/dist/services/telegram.js +1 -158
- package/dist/services/timing-safe-bearer.js +1 -51
- package/dist/services/tool-discovery.js +1 -214
- package/dist/services/trends.js +1 -580
- package/dist/services/updater.js +1 -291
- package/dist/services/usage-tracker.js +1 -144
- package/dist/services/users.js +1 -271
- package/dist/services/voice.js +1 -104
- package/dist/services/watchdog-brake.js +1 -154
- package/dist/services/watchdog.js +1 -311
- package/dist/services/workspaces.js +1 -276
- package/dist/tui/index.js +1 -667
- package/dist/util/console-formatter.js +1 -109
- package/dist/util/debounce.js +1 -24
- package/dist/util/telegram-error-filter.js +1 -62
- package/dist/version.js +1 -24
- package/dist/web/bind-strategy.js +1 -42
- package/dist/web/canvas.js +1 -30
- package/dist/web/doctor-api.js +1 -604
- package/dist/web/openai-compat.js +1 -252
- package/dist/web/server.js +1 -1831
- package/dist/web/setup-api.js +1 -1101
- package/package.json +5 -2
- package/dist/.metadata_never_index +0 -0
package/dist/web/server.js
CHANGED
|
@@ -1,1831 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Web Server — Local dashboard for Alvin Bot.
|
|
3
|
-
*
|
|
4
|
-
* Provides:
|
|
5
|
-
* - Static file serving (web/public/)
|
|
6
|
-
* - WebSocket for real-time chat + streaming
|
|
7
|
-
* - REST API for settings, memory, sessions, etc.
|
|
8
|
-
* - Simple password auth (WEB_PASSWORD env var)
|
|
9
|
-
*/
|
|
10
|
-
import http from "http";
|
|
11
|
-
import fs from "fs";
|
|
12
|
-
import path from "path";
|
|
13
|
-
import { resolve } from "path";
|
|
14
|
-
import { execSync } from "child_process";
|
|
15
|
-
import { WebSocketServer, WebSocket } from "ws";
|
|
16
|
-
import { getRegistry } from "../engine.js";
|
|
17
|
-
import { getSession, resetSession, getAllSessions } from "../services/session.js";
|
|
18
|
-
import { getMemoryStats, loadLongTermMemory, loadDailyLog } from "../services/memory.js";
|
|
19
|
-
import { getIndexStats } from "../services/embeddings.js";
|
|
20
|
-
import { getLoadedPlugins } from "../services/plugins.js";
|
|
21
|
-
import { getMCPStatus } from "../services/mcp.js";
|
|
22
|
-
import { listProfiles } from "../services/users.js";
|
|
23
|
-
import { listCustomTools, getCustomTools, executeCustomTool } from "../services/custom-tools.js";
|
|
24
|
-
import { buildSystemPrompt, reloadSoul, getSoulContent } from "../services/personality.js";
|
|
25
|
-
import { config } from "../config.js";
|
|
26
|
-
import { handleSetupAPI } from "./setup-api.js";
|
|
27
|
-
import { handleDoctorAPI } from "./doctor-api.js";
|
|
28
|
-
import { handleOpenAICompat } from "./openai-compat.js";
|
|
29
|
-
import { addCanvasClient } from "./canvas.js";
|
|
30
|
-
import { BOT_ROOT, ENV_FILE, PUBLIC_DIR, MEMORY_DIR, MEMORY_FILE, SOUL_FILE, DATA_DIR, MCP_CONFIG, SKILLS_DIR } from "../paths.js";
|
|
31
|
-
import { writeSecure } from "../services/file-permissions.js";
|
|
32
|
-
import { timingSafeBearerMatch } from "../services/timing-safe-bearer.js";
|
|
33
|
-
import { broadcast } from "../services/broadcast.js";
|
|
34
|
-
import { BOT_VERSION } from "../version.js";
|
|
35
|
-
import { decideNextBindAction } from "./bind-strategy.js";
|
|
36
|
-
const WEB_PORT = parseInt(process.env.WEB_PORT || "3100");
|
|
37
|
-
/** Tuning for the bind loop. Walk the port ladder `MAX_PORT_TRIES` times
|
|
38
|
-
* then fall back to a `BACKGROUND_RETRY_MS` idle loop — the bot keeps
|
|
39
|
-
* running on Telegram either way; see bind-strategy.ts for the pure
|
|
40
|
-
* decision logic. */
|
|
41
|
-
const MAX_PORT_TRIES = 20;
|
|
42
|
-
const BACKGROUND_RETRY_MS = 30_000;
|
|
43
|
-
/** Current live http.Server, if one has successfully bound. */
|
|
44
|
-
let currentServer = null;
|
|
45
|
-
/** Current live WebSocketServer attached to currentServer. */
|
|
46
|
-
let wsServerRef = null;
|
|
47
|
-
/** Background-retry timer handle — set when the bind loop is in its
|
|
48
|
-
* idle wait between cycles, cleared when stopWebServer() cancels. */
|
|
49
|
-
let bindRetryTimer = null;
|
|
50
|
-
/** Flag flipped by stopWebServer(). Every bind-loop callback checks
|
|
51
|
-
* this and exits silently if set, so stop is truly terminal. */
|
|
52
|
-
let stopRequested = false;
|
|
53
|
-
const WEB_PASSWORD = process.env.WEB_PASSWORD || "";
|
|
54
|
-
/** The actual port the Web UI is running on (may differ from WEB_PORT if busy). */
|
|
55
|
-
let actualWebPort = WEB_PORT;
|
|
56
|
-
// ── MIME Types ──────────────────────────────────────────
|
|
57
|
-
const MIME = {
|
|
58
|
-
".html": "text/html",
|
|
59
|
-
".css": "text/css",
|
|
60
|
-
".js": "application/javascript",
|
|
61
|
-
".json": "application/json",
|
|
62
|
-
".png": "image/png",
|
|
63
|
-
".jpg": "image/jpeg",
|
|
64
|
-
".svg": "image/svg+xml",
|
|
65
|
-
".ico": "image/x-icon",
|
|
66
|
-
};
|
|
67
|
-
// ── Auth ────────────────────────────────────────────────
|
|
68
|
-
const activeSessions = new Set();
|
|
69
|
-
function generateToken() {
|
|
70
|
-
return Array.from(crypto.getRandomValues(new Uint8Array(32)))
|
|
71
|
-
.map(b => b.toString(16).padStart(2, "0")).join("");
|
|
72
|
-
}
|
|
73
|
-
function checkAuth(req) {
|
|
74
|
-
if (!WEB_PASSWORD)
|
|
75
|
-
return true; // No password = open access
|
|
76
|
-
const cookie = req.headers.cookie || "";
|
|
77
|
-
const token = cookie.match(/alvinbot_token=([a-f0-9]+)/)?.[1];
|
|
78
|
-
return token ? activeSessions.has(token) : false;
|
|
79
|
-
}
|
|
80
|
-
/** Returns true when the server is exposed to non-loopback addresses but
|
|
81
|
-
* WEB_PASSWORD is empty. In that state every mutating / exec endpoint is
|
|
82
|
-
* hard-refused regardless of any session cookie. */
|
|
83
|
-
function isExposedWithoutPassword() {
|
|
84
|
-
if (WEB_PASSWORD)
|
|
85
|
-
return false; // password set → normal auth path
|
|
86
|
-
const h = config.webHost;
|
|
87
|
-
// loopback addresses are safe even without a password
|
|
88
|
-
if (!h || h === "127.0.0.1" || h === "::1" || h === "localhost")
|
|
89
|
-
return false;
|
|
90
|
-
return true; // non-loopback + no password = exposed
|
|
91
|
-
}
|
|
92
|
-
// ── REST API ────────────────────────────────────────────
|
|
93
|
-
async function handleAPI(req, res, urlPath, body) {
|
|
94
|
-
res.setHeader("Content-Type", "application/json");
|
|
95
|
-
// POST /api/login
|
|
96
|
-
if (urlPath === "/api/login" && req.method === "POST") {
|
|
97
|
-
try {
|
|
98
|
-
const { password } = JSON.parse(body);
|
|
99
|
-
// v5.x — refuse login entirely when WEB_PASSWORD is not set.
|
|
100
|
-
// Previously `!WEB_PASSWORD || password === WEB_PASSWORD` minted a
|
|
101
|
-
// session for ANY input (including "") when the env var was absent,
|
|
102
|
-
// effectively making every unauthenticated request a valid session.
|
|
103
|
-
// timingSafeBearerMatch already rejects empty expectedToken → reuse it
|
|
104
|
-
// by wrapping the provided password as a synthetic "Bearer <pw>" header.
|
|
105
|
-
const authOk = timingSafeBearerMatch(`Bearer ${password}`, WEB_PASSWORD);
|
|
106
|
-
if (authOk) {
|
|
107
|
-
const token = generateToken();
|
|
108
|
-
activeSessions.add(token);
|
|
109
|
-
res.setHeader("Set-Cookie", `alvinbot_token=${token}; Path=/; HttpOnly; SameSite=Strict; Max-Age=86400`);
|
|
110
|
-
res.end(JSON.stringify({ ok: true }));
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
res.statusCode = 401;
|
|
114
|
-
res.end(JSON.stringify({ error: "Wrong password" }));
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
res.statusCode = 400;
|
|
119
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
120
|
-
}
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
// POST /api/webhook — external trigger endpoint with bearer auth (no cookie auth needed)
|
|
124
|
-
if (urlPath === "/api/webhook" && req.method === "POST") {
|
|
125
|
-
if (!config.webhookEnabled) {
|
|
126
|
-
res.writeHead(404);
|
|
127
|
-
res.end(JSON.stringify({ error: "Webhooks disabled" }));
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
// v4.12.2 — timing-safe bearer token comparison. Previously used
|
|
131
|
-
// naive !== which leaks comparison position via timing side-channel.
|
|
132
|
-
if (!timingSafeBearerMatch(req.headers.authorization, config.webhookToken ?? "")) {
|
|
133
|
-
res.writeHead(401);
|
|
134
|
-
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
const payload = JSON.parse(body);
|
|
139
|
-
if (!payload.message) {
|
|
140
|
-
res.writeHead(400);
|
|
141
|
-
res.end(JSON.stringify({ error: "Missing message field" }));
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const channel = payload.channel || "telegram";
|
|
145
|
-
const chatId = payload.chatId || String(config.allowedUsers[0] || "");
|
|
146
|
-
const { enqueue } = await import("../services/delivery-queue.js");
|
|
147
|
-
const id = enqueue(channel, chatId, `[Webhook: ${payload.event || "unknown"}] ${payload.message}`);
|
|
148
|
-
res.writeHead(200);
|
|
149
|
-
res.end(JSON.stringify({ ok: true, queued: id }));
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
res.writeHead(400);
|
|
153
|
-
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
154
|
-
}
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
// Auth check for all other API routes
|
|
158
|
-
if (!checkAuth(req)) {
|
|
159
|
-
res.statusCode = 401;
|
|
160
|
-
res.end(JSON.stringify({ error: "Not authenticated" }));
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
// Hard gate: when the web server is reachable from non-loopback addresses
|
|
164
|
-
// and WEB_PASSWORD is empty, mutating/exec endpoints are refused outright.
|
|
165
|
-
// Local dev (127.0.0.1 + no password) is intentionally unaffected.
|
|
166
|
-
// Endpoints that write files, execute commands, or modify configuration
|
|
167
|
-
// are in scope; read-only status/config endpoints are not gated.
|
|
168
|
-
const EXPOSED_DANGEROUS_ROUTES = new Set([
|
|
169
|
-
"/api/terminal",
|
|
170
|
-
"/api/skills/create",
|
|
171
|
-
"/api/skills/update",
|
|
172
|
-
"/api/skills/delete",
|
|
173
|
-
"/api/files/save",
|
|
174
|
-
"/api/files/delete",
|
|
175
|
-
"/api/env/set",
|
|
176
|
-
"/api/setup-wizard",
|
|
177
|
-
"/api/soul/save",
|
|
178
|
-
"/api/memory/save",
|
|
179
|
-
"/api/memory/delete",
|
|
180
|
-
"/api/session/reset",
|
|
181
|
-
// cron — /api/cron/add was wrong; real route is /api/cron/create
|
|
182
|
-
"/api/cron/create",
|
|
183
|
-
"/api/cron/update",
|
|
184
|
-
"/api/cron/delete",
|
|
185
|
-
"/api/cron/toggle",
|
|
186
|
-
"/api/cron/run",
|
|
187
|
-
"/api/plugin/install",
|
|
188
|
-
"/api/plugin/uninstall",
|
|
189
|
-
// shell / process exec
|
|
190
|
-
"/api/tools/execute",
|
|
191
|
-
"/api/sudo/exec",
|
|
192
|
-
"/api/sudo/setup",
|
|
193
|
-
"/api/sudo/admin-dialog",
|
|
194
|
-
"/api/sudo/revoke",
|
|
195
|
-
// key / env writes
|
|
196
|
-
"/api/providers/set-key",
|
|
197
|
-
"/api/providers/set-primary",
|
|
198
|
-
"/api/providers/set-fallbacks",
|
|
199
|
-
"/api/providers/add-custom",
|
|
200
|
-
"/api/providers/remove-custom",
|
|
201
|
-
// platform config writes (writes ENV vars / runs npm install)
|
|
202
|
-
"/api/platforms/configure",
|
|
203
|
-
"/api/platforms/install-deps",
|
|
204
|
-
// provider / fallback order writes
|
|
205
|
-
"/api/fallback",
|
|
206
|
-
"/api/fallback/move",
|
|
207
|
-
// MCP config writes
|
|
208
|
-
"/api/mcp/add",
|
|
209
|
-
"/api/mcp/remove",
|
|
210
|
-
// process restart (DoS vector)
|
|
211
|
-
"/api/restart",
|
|
212
|
-
// model switching (affects all users)
|
|
213
|
-
"/api/models/switch",
|
|
214
|
-
// WhatsApp auth / config writes
|
|
215
|
-
"/api/whatsapp/disconnect",
|
|
216
|
-
"/api/whatsapp/group-rules",
|
|
217
|
-
]);
|
|
218
|
-
if (isExposedWithoutPassword() && EXPOSED_DANGEROUS_ROUTES.has(urlPath)) {
|
|
219
|
-
res.statusCode = 403;
|
|
220
|
-
res.end(JSON.stringify({ error: "refused: web exposed without WEB_PASSWORD" }));
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
// ── Setup APIs (platforms + models) ─────────────────
|
|
224
|
-
const handled = await handleSetupAPI(req, res, urlPath, body);
|
|
225
|
-
if (handled)
|
|
226
|
-
return;
|
|
227
|
-
// ── Doctor & Backup APIs ──────────────────────────
|
|
228
|
-
const doctorHandled = await handleDoctorAPI(req, res, urlPath, body);
|
|
229
|
-
if (doctorHandled)
|
|
230
|
-
return;
|
|
231
|
-
// GET /api/setup-check — is the bot fully configured?
|
|
232
|
-
if (urlPath === "/api/setup-check") {
|
|
233
|
-
const envPath = ENV_FILE;
|
|
234
|
-
let env = {};
|
|
235
|
-
try {
|
|
236
|
-
const lines = fs.readFileSync(envPath, "utf-8").split("\n");
|
|
237
|
-
for (const line of lines) {
|
|
238
|
-
if (line.startsWith("#") || !line.includes("="))
|
|
239
|
-
continue;
|
|
240
|
-
const idx = line.indexOf("=");
|
|
241
|
-
env[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
catch { }
|
|
245
|
-
const hasBotToken = !!(env.BOT_TOKEN || process.env.BOT_TOKEN);
|
|
246
|
-
const hasAllowedUsers = !!(env.ALLOWED_USERS || process.env.ALLOWED_USERS);
|
|
247
|
-
const hasPrimaryProvider = !!(env.PRIMARY_PROVIDER || process.env.PRIMARY_PROVIDER);
|
|
248
|
-
// Check which providers have keys
|
|
249
|
-
const providerKeys = {
|
|
250
|
-
groq: !!(env.GROQ_API_KEY || process.env.GROQ_API_KEY),
|
|
251
|
-
openai: !!(env.OPENAI_API_KEY || process.env.OPENAI_API_KEY),
|
|
252
|
-
google: !!(env.GOOGLE_API_KEY || process.env.GOOGLE_API_KEY),
|
|
253
|
-
nvidia: !!(env.NVIDIA_API_KEY || process.env.NVIDIA_API_KEY),
|
|
254
|
-
anthropic: !!(env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY),
|
|
255
|
-
openrouter: !!(env.OPENROUTER_API_KEY || process.env.OPENROUTER_API_KEY),
|
|
256
|
-
};
|
|
257
|
-
const hasAnyProvider = hasPrimaryProvider || Object.values(providerKeys).some(Boolean);
|
|
258
|
-
// Check Claude CLI
|
|
259
|
-
let claudeCliInstalled = false;
|
|
260
|
-
try {
|
|
261
|
-
const { execSync } = await import("child_process");
|
|
262
|
-
execSync("claude --version", { timeout: 5000, stdio: "pipe" });
|
|
263
|
-
claudeCliInstalled = true;
|
|
264
|
-
}
|
|
265
|
-
catch { }
|
|
266
|
-
const isComplete = hasBotToken && hasAllowedUsers && hasAnyProvider;
|
|
267
|
-
res.end(JSON.stringify({
|
|
268
|
-
isComplete,
|
|
269
|
-
steps: {
|
|
270
|
-
telegram: { done: hasBotToken && hasAllowedUsers, botToken: hasBotToken, allowedUsers: hasAllowedUsers },
|
|
271
|
-
provider: { done: hasAnyProvider, primary: env.PRIMARY_PROVIDER || process.env.PRIMARY_PROVIDER || "", keys: providerKeys, claudeCli: claudeCliInstalled },
|
|
272
|
-
},
|
|
273
|
-
}));
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
// POST /api/setup-wizard — save all setup data at once (first-run wizard)
|
|
277
|
-
if (urlPath === "/api/setup-wizard" && req.method === "POST") {
|
|
278
|
-
try {
|
|
279
|
-
const data = JSON.parse(body);
|
|
280
|
-
const envPath = ENV_FILE;
|
|
281
|
-
let content = fs.existsSync(envPath) ? fs.readFileSync(envPath, "utf-8") : "";
|
|
282
|
-
const setEnv = (key, value) => {
|
|
283
|
-
// M6 (centralized): reject values containing newline characters
|
|
284
|
-
if (/[\n\r]/.test(value))
|
|
285
|
-
throw new Error("env value must not contain newline characters");
|
|
286
|
-
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
287
|
-
if (regex.test(content)) {
|
|
288
|
-
content = content.replace(regex, `${key}=${value}`);
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
content = content.trimEnd() + `\n${key}=${value}\n`;
|
|
292
|
-
}
|
|
293
|
-
process.env[key] = value;
|
|
294
|
-
};
|
|
295
|
-
// Step 1: Telegram
|
|
296
|
-
if (data.botToken)
|
|
297
|
-
setEnv("BOT_TOKEN", data.botToken);
|
|
298
|
-
if (data.allowedUsers)
|
|
299
|
-
setEnv("ALLOWED_USERS", data.allowedUsers);
|
|
300
|
-
// Step 2: Provider
|
|
301
|
-
if (data.primaryProvider)
|
|
302
|
-
setEnv("PRIMARY_PROVIDER", data.primaryProvider);
|
|
303
|
-
if (data.apiKey && data.apiKeyEnv)
|
|
304
|
-
setEnv(data.apiKeyEnv, data.apiKey);
|
|
305
|
-
// Step 3: Optional
|
|
306
|
-
if (data.webPassword)
|
|
307
|
-
setEnv("WEB_PASSWORD", data.webPassword);
|
|
308
|
-
fs.writeFileSync(envPath, content);
|
|
309
|
-
res.end(JSON.stringify({ ok: true, note: "Setup complete! Restart needed." }));
|
|
310
|
-
}
|
|
311
|
-
catch (e) {
|
|
312
|
-
res.statusCode = 400;
|
|
313
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
314
|
-
}
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
// POST /api/validate-bot-token — validate a Telegram bot token
|
|
318
|
-
if (urlPath === "/api/validate-bot-token" && req.method === "POST") {
|
|
319
|
-
try {
|
|
320
|
-
const { token } = JSON.parse(body);
|
|
321
|
-
if (!token || !token.includes(":")) {
|
|
322
|
-
res.end(JSON.stringify({ ok: false, error: "Invalid token format" }));
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
const tgRes = await fetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
326
|
-
const tgData = await tgRes.json();
|
|
327
|
-
if (tgData.ok) {
|
|
328
|
-
res.end(JSON.stringify({ ok: true, bot: { username: tgData.result.username, firstName: tgData.result.first_name, id: tgData.result.id } }));
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
res.end(JSON.stringify({ ok: false, error: tgData.description || "Invalid token" }));
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
catch (e) {
|
|
335
|
-
res.end(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
336
|
-
}
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
// GET /api/status
|
|
340
|
-
if (urlPath === "/api/status") {
|
|
341
|
-
let modelInfo = { name: "Not configured", model: "none", status: "unconfigured" };
|
|
342
|
-
try {
|
|
343
|
-
const registry = getRegistry();
|
|
344
|
-
const active = registry.getActive().getInfo();
|
|
345
|
-
modelInfo = { name: active.name, model: active.model, status: active.status };
|
|
346
|
-
}
|
|
347
|
-
catch { /* engine not initialized — no provider configured */ }
|
|
348
|
-
const memory = getMemoryStats();
|
|
349
|
-
const index = getIndexStats();
|
|
350
|
-
const plugins = getLoadedPlugins();
|
|
351
|
-
const mcp = getMCPStatus();
|
|
352
|
-
const users = listProfiles();
|
|
353
|
-
const tools = listCustomTools();
|
|
354
|
-
// Aggregate token usage across all sessions
|
|
355
|
-
const { getAllSessions } = await import("../services/session.js");
|
|
356
|
-
const allSessions = getAllSessions();
|
|
357
|
-
let totalInputTokens = 0, totalOutputTokens = 0, totalCost = 0;
|
|
358
|
-
for (const s of allSessions.values()) {
|
|
359
|
-
totalInputTokens += s.totalInputTokens || 0;
|
|
360
|
-
totalOutputTokens += s.totalOutputTokens || 0;
|
|
361
|
-
totalCost += s.totalCost || 0;
|
|
362
|
-
}
|
|
363
|
-
const { config: appConfig } = await import("../config.js");
|
|
364
|
-
res.end(JSON.stringify({
|
|
365
|
-
bot: { version: BOT_VERSION, uptime: process.uptime() },
|
|
366
|
-
model: modelInfo,
|
|
367
|
-
memory: { ...memory, vectors: index.entries, indexSize: index.sizeBytes },
|
|
368
|
-
plugins: plugins.length,
|
|
369
|
-
mcp: mcp.length,
|
|
370
|
-
users: users.length,
|
|
371
|
-
tools: tools.length,
|
|
372
|
-
tokens: {
|
|
373
|
-
totalInput: totalInputTokens,
|
|
374
|
-
totalOutput: totalOutputTokens,
|
|
375
|
-
total: totalInputTokens + totalOutputTokens,
|
|
376
|
-
totalCost,
|
|
377
|
-
},
|
|
378
|
-
setup: {
|
|
379
|
-
telegram: !!appConfig.botToken,
|
|
380
|
-
provider: modelInfo.status !== "unconfigured",
|
|
381
|
-
},
|
|
382
|
-
}));
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
// GET /api/models
|
|
386
|
-
if (urlPath === "/api/models") {
|
|
387
|
-
const registry = getRegistry();
|
|
388
|
-
registry.listAll().then(models => {
|
|
389
|
-
res.end(JSON.stringify({ models, active: registry.getActiveKey() }));
|
|
390
|
-
});
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
// POST /api/models/switch
|
|
394
|
-
if (urlPath === "/api/models/switch" && req.method === "POST") {
|
|
395
|
-
try {
|
|
396
|
-
const { key } = JSON.parse(body);
|
|
397
|
-
const registry = getRegistry();
|
|
398
|
-
const ok = registry.switchTo(key);
|
|
399
|
-
res.end(JSON.stringify({ ok, active: registry.getActiveKey() }));
|
|
400
|
-
}
|
|
401
|
-
catch {
|
|
402
|
-
res.statusCode = 400;
|
|
403
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
404
|
-
}
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
// GET /api/fallback — Get fallback order + health
|
|
408
|
-
if (urlPath === "/api/fallback" && req.method === "GET") {
|
|
409
|
-
try {
|
|
410
|
-
const { getFallbackOrder } = await import("../services/fallback-order.js");
|
|
411
|
-
const { getHealthStatus, isFailedOver } = await import("../services/heartbeat.js");
|
|
412
|
-
const registry = getRegistry();
|
|
413
|
-
const providers = await registry.listAll();
|
|
414
|
-
res.end(JSON.stringify({
|
|
415
|
-
order: getFallbackOrder(),
|
|
416
|
-
health: getHealthStatus(),
|
|
417
|
-
failedOver: isFailedOver(),
|
|
418
|
-
activeProvider: registry.getActiveKey(),
|
|
419
|
-
availableProviders: providers.map(p => ({ key: p.key, name: p.name, status: p.status })),
|
|
420
|
-
}));
|
|
421
|
-
}
|
|
422
|
-
catch (err) {
|
|
423
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
424
|
-
}
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
// POST /api/fallback — Set fallback order
|
|
428
|
-
if (urlPath === "/api/fallback" && req.method === "POST") {
|
|
429
|
-
try {
|
|
430
|
-
const { primary, fallbacks } = JSON.parse(body);
|
|
431
|
-
const { setFallbackOrder } = await import("../services/fallback-order.js");
|
|
432
|
-
const result = setFallbackOrder(primary, fallbacks, "webui");
|
|
433
|
-
res.end(JSON.stringify({ ok: true, order: result }));
|
|
434
|
-
}
|
|
435
|
-
catch (err) {
|
|
436
|
-
res.statusCode = 400;
|
|
437
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
438
|
-
}
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
// POST /api/fallback/move — Move provider up/down
|
|
442
|
-
if (urlPath === "/api/fallback/move" && req.method === "POST") {
|
|
443
|
-
try {
|
|
444
|
-
const { key, direction } = JSON.parse(body);
|
|
445
|
-
const fb = await import("../services/fallback-order.js");
|
|
446
|
-
const result = direction === "up" ? fb.moveUp(key, "webui") : fb.moveDown(key, "webui");
|
|
447
|
-
res.end(JSON.stringify({ ok: true, order: result }));
|
|
448
|
-
}
|
|
449
|
-
catch (err) {
|
|
450
|
-
res.statusCode = 400;
|
|
451
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
452
|
-
}
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
// GET /api/heartbeat — Health status
|
|
456
|
-
if (urlPath === "/api/heartbeat") {
|
|
457
|
-
try {
|
|
458
|
-
const { getHealthStatus, isFailedOver } = await import("../services/heartbeat.js");
|
|
459
|
-
res.end(JSON.stringify({
|
|
460
|
-
health: getHealthStatus(),
|
|
461
|
-
failedOver: isFailedOver(),
|
|
462
|
-
}));
|
|
463
|
-
}
|
|
464
|
-
catch (err) {
|
|
465
|
-
res.end(JSON.stringify({ health: [], failedOver: false }));
|
|
466
|
-
}
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
// GET /api/memory
|
|
470
|
-
if (urlPath === "/api/memory") {
|
|
471
|
-
const ltm = loadLongTermMemory();
|
|
472
|
-
const todayLog = loadDailyLog();
|
|
473
|
-
const stats = getMemoryStats();
|
|
474
|
-
const index = getIndexStats();
|
|
475
|
-
// List daily log files
|
|
476
|
-
let dailyFiles = [];
|
|
477
|
-
try {
|
|
478
|
-
dailyFiles = fs.readdirSync(MEMORY_DIR)
|
|
479
|
-
.filter(f => f.endsWith(".md") && !f.startsWith("."))
|
|
480
|
-
.sort()
|
|
481
|
-
.reverse();
|
|
482
|
-
}
|
|
483
|
-
catch { /* empty */ }
|
|
484
|
-
res.end(JSON.stringify({
|
|
485
|
-
longTermMemory: ltm,
|
|
486
|
-
todayLog,
|
|
487
|
-
dailyFiles,
|
|
488
|
-
stats,
|
|
489
|
-
index: { entries: index.entries, files: index.files, sizeBytes: index.sizeBytes },
|
|
490
|
-
}));
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
// GET /api/memory/:file
|
|
494
|
-
if (urlPath.startsWith("/api/memory/")) {
|
|
495
|
-
const file = urlPath.slice(12);
|
|
496
|
-
if (file.includes("..") || !file.endsWith(".md")) {
|
|
497
|
-
res.statusCode = 400;
|
|
498
|
-
res.end(JSON.stringify({ error: "Invalid file" }));
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
try {
|
|
502
|
-
const content = fs.readFileSync(resolve(MEMORY_DIR, file), "utf-8");
|
|
503
|
-
res.end(JSON.stringify({ file, content }));
|
|
504
|
-
}
|
|
505
|
-
catch {
|
|
506
|
-
res.statusCode = 404;
|
|
507
|
-
res.end(JSON.stringify({ error: "File not found" }));
|
|
508
|
-
}
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
// POST /api/memory/save
|
|
512
|
-
if (urlPath === "/api/memory/save" && req.method === "POST") {
|
|
513
|
-
try {
|
|
514
|
-
const { file, content } = JSON.parse(body);
|
|
515
|
-
if (file === "MEMORY.md") {
|
|
516
|
-
fs.writeFileSync(MEMORY_FILE, content);
|
|
517
|
-
}
|
|
518
|
-
else if (file.endsWith(".md") && !file.includes("..")) {
|
|
519
|
-
fs.writeFileSync(resolve(MEMORY_DIR, file), content);
|
|
520
|
-
}
|
|
521
|
-
else {
|
|
522
|
-
res.statusCode = 400;
|
|
523
|
-
res.end(JSON.stringify({ error: "Invalid file" }));
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
res.end(JSON.stringify({ ok: true }));
|
|
527
|
-
}
|
|
528
|
-
catch {
|
|
529
|
-
res.statusCode = 400;
|
|
530
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
531
|
-
}
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
// GET /api/plugins
|
|
535
|
-
if (urlPath === "/api/plugins") {
|
|
536
|
-
res.end(JSON.stringify({ plugins: getLoadedPlugins() }));
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
// v4.12.0 — Workspace overview: registry + per-workspace cost breakdown
|
|
540
|
-
if (urlPath === "/api/workspaces") {
|
|
541
|
-
try {
|
|
542
|
-
const { listWorkspaces, getDefaultWorkspace } = await import("../services/workspaces.js");
|
|
543
|
-
const { getCostByWorkspace } = await import("../services/session.js");
|
|
544
|
-
const costs = getCostByWorkspace();
|
|
545
|
-
const registered = listWorkspaces();
|
|
546
|
-
const all = [getDefaultWorkspace(), ...registered];
|
|
547
|
-
const payload = all.map((ws) => ({
|
|
548
|
-
name: ws.name,
|
|
549
|
-
purpose: ws.purpose,
|
|
550
|
-
emoji: ws.emoji ?? null,
|
|
551
|
-
color: ws.color ?? null,
|
|
552
|
-
cwd: ws.cwd,
|
|
553
|
-
channels: ws.channels,
|
|
554
|
-
stats: costs[ws.name] ?? { totalCost: 0, sessionCount: 0, messageCount: 0, toolUseCount: 0 },
|
|
555
|
-
}));
|
|
556
|
-
res.end(JSON.stringify({ workspaces: payload }));
|
|
557
|
-
}
|
|
558
|
-
catch (err) {
|
|
559
|
-
res.statusCode = 500;
|
|
560
|
-
res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
|
|
561
|
-
}
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
// GET /api/users — Enhanced with session data
|
|
565
|
-
if (urlPath === "/api/users" && req.method === "GET") {
|
|
566
|
-
const { getAllSessions } = await import("../services/session.js");
|
|
567
|
-
const profiles = listProfiles();
|
|
568
|
-
const sessions = getAllSessions();
|
|
569
|
-
const sessionMap = new Map(Array.from(sessions.entries()).map(([k, s]) => [Number(k), s]));
|
|
570
|
-
const enriched = profiles.map(p => {
|
|
571
|
-
const session = sessionMap.get(p.userId);
|
|
572
|
-
return {
|
|
573
|
-
...p,
|
|
574
|
-
session: session ? {
|
|
575
|
-
isProcessing: session.isProcessing,
|
|
576
|
-
totalCost: session.totalCost,
|
|
577
|
-
historyLength: session.history.length,
|
|
578
|
-
effort: session.effort,
|
|
579
|
-
voiceReply: session.voiceReply,
|
|
580
|
-
startedAt: session.startedAt,
|
|
581
|
-
messageCount: session.messageCount,
|
|
582
|
-
toolUseCount: session.toolUseCount,
|
|
583
|
-
workingDir: session.workingDir,
|
|
584
|
-
hasActiveQuery: !!session.abortController,
|
|
585
|
-
queuedMessages: session.messageQueue.length,
|
|
586
|
-
} : null,
|
|
587
|
-
};
|
|
588
|
-
});
|
|
589
|
-
res.end(JSON.stringify({ users: enriched }));
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
// DELETE /api/users/:id — Kill session + delete user data
|
|
593
|
-
if (urlPath.startsWith("/api/users/") && req.method === "DELETE") {
|
|
594
|
-
// Pattern route — gate here since EXPOSED_DANGEROUS_ROUTES can't express :id
|
|
595
|
-
if (isExposedWithoutPassword()) {
|
|
596
|
-
res.statusCode = 403;
|
|
597
|
-
res.end(JSON.stringify({ error: "refused: web exposed without WEB_PASSWORD" }));
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
const userId = parseInt(urlPath.split("/").pop() || "0");
|
|
601
|
-
if (!userId) {
|
|
602
|
-
res.statusCode = 400;
|
|
603
|
-
res.end(JSON.stringify({ error: "Invalid user ID" }));
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
const { deleteUser } = await import("../services/users.js");
|
|
607
|
-
const result = deleteUser(userId);
|
|
608
|
-
res.end(JSON.stringify({ ok: true, ...result }));
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
// GET /api/tools
|
|
612
|
-
if (urlPath === "/api/tools") {
|
|
613
|
-
const tools = getCustomTools();
|
|
614
|
-
res.end(JSON.stringify({ tools }));
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
// POST /api/tools/execute — run a tool by name
|
|
618
|
-
if (urlPath === "/api/tools/execute" && req.method === "POST") {
|
|
619
|
-
try {
|
|
620
|
-
const { name, params } = JSON.parse(body);
|
|
621
|
-
if (!name) {
|
|
622
|
-
res.statusCode = 400;
|
|
623
|
-
res.end(JSON.stringify({ error: "No tool name" }));
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
const output = await executeCustomTool(name, params || {});
|
|
627
|
-
res.end(JSON.stringify({ ok: true, output }));
|
|
628
|
-
}
|
|
629
|
-
catch (err) {
|
|
630
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
631
|
-
res.end(JSON.stringify({ error }));
|
|
632
|
-
}
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
// ── MCP Management ─────────────────────────────────────
|
|
636
|
-
// GET /api/mcp — list MCP servers + tools
|
|
637
|
-
if (urlPath === "/api/mcp") {
|
|
638
|
-
const { getMCPStatus, getMCPTools, hasMCPConfig } = await import("../services/mcp.js");
|
|
639
|
-
const servers = getMCPStatus();
|
|
640
|
-
const tools = getMCPTools();
|
|
641
|
-
// Read raw config for editing
|
|
642
|
-
const configPath = MCP_CONFIG;
|
|
643
|
-
let rawConfig = { servers: {} };
|
|
644
|
-
try {
|
|
645
|
-
rawConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
646
|
-
}
|
|
647
|
-
catch { }
|
|
648
|
-
res.end(JSON.stringify({ servers, tools, config: rawConfig, hasConfig: hasMCPConfig() }));
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
// POST /api/mcp/add — add a new MCP server
|
|
652
|
-
if (urlPath === "/api/mcp/add" && req.method === "POST") {
|
|
653
|
-
try {
|
|
654
|
-
const { name, command, args, url: serverUrl, env, headers } = JSON.parse(body);
|
|
655
|
-
if (!name) {
|
|
656
|
-
res.statusCode = 400;
|
|
657
|
-
res.end(JSON.stringify({ error: "Name required" }));
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
const configPath = MCP_CONFIG;
|
|
661
|
-
let config = { servers: {} };
|
|
662
|
-
try {
|
|
663
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
664
|
-
}
|
|
665
|
-
catch { }
|
|
666
|
-
const entry = {};
|
|
667
|
-
if (command) {
|
|
668
|
-
entry.command = command;
|
|
669
|
-
entry.args = args || [];
|
|
670
|
-
if (env)
|
|
671
|
-
entry.env = env;
|
|
672
|
-
}
|
|
673
|
-
else if (serverUrl) {
|
|
674
|
-
entry.url = serverUrl;
|
|
675
|
-
if (headers)
|
|
676
|
-
entry.headers = headers;
|
|
677
|
-
}
|
|
678
|
-
else {
|
|
679
|
-
res.statusCode = 400;
|
|
680
|
-
res.end(JSON.stringify({ error: "command or url required" }));
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
config.servers[name] = entry;
|
|
684
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
685
|
-
res.end(JSON.stringify({ ok: true, note: "Restart needed to connect." }));
|
|
686
|
-
}
|
|
687
|
-
catch (e) {
|
|
688
|
-
res.statusCode = 400;
|
|
689
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
690
|
-
}
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
// POST /api/mcp/remove — remove an MCP server
|
|
694
|
-
if (urlPath === "/api/mcp/remove" && req.method === "POST") {
|
|
695
|
-
try {
|
|
696
|
-
const { name } = JSON.parse(body);
|
|
697
|
-
const configPath = MCP_CONFIG;
|
|
698
|
-
let config = { servers: {} };
|
|
699
|
-
try {
|
|
700
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
701
|
-
}
|
|
702
|
-
catch { }
|
|
703
|
-
delete config.servers[name];
|
|
704
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
705
|
-
res.end(JSON.stringify({ ok: true }));
|
|
706
|
-
}
|
|
707
|
-
catch (e) {
|
|
708
|
-
res.statusCode = 400;
|
|
709
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
710
|
-
}
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
// GET /api/mcp/discover — auto-discover MCP servers on the system
|
|
714
|
-
if (urlPath === "/api/mcp/discover") {
|
|
715
|
-
const discovered = [];
|
|
716
|
-
const { execSync } = await import("child_process");
|
|
717
|
-
// Check for common MCP server npm packages
|
|
718
|
-
const knownServers = [
|
|
719
|
-
{ pkg: "@modelcontextprotocol/server-filesystem", name: "filesystem", args: ["/tmp"] },
|
|
720
|
-
{ pkg: "@modelcontextprotocol/server-brave-search", name: "brave-search", args: [] },
|
|
721
|
-
{ pkg: "@modelcontextprotocol/server-github", name: "github", args: [] },
|
|
722
|
-
{ pkg: "@modelcontextprotocol/server-postgres", name: "postgres", args: [] },
|
|
723
|
-
{ pkg: "@modelcontextprotocol/server-sqlite", name: "sqlite", args: [] },
|
|
724
|
-
{ pkg: "@modelcontextprotocol/server-slack", name: "slack", args: [] },
|
|
725
|
-
{ pkg: "@modelcontextprotocol/server-memory", name: "memory", args: [] },
|
|
726
|
-
{ pkg: "@modelcontextprotocol/server-puppeteer", name: "puppeteer", args: [] },
|
|
727
|
-
{ pkg: "@modelcontextprotocol/server-fetch", name: "web-fetch", args: [] },
|
|
728
|
-
{ pkg: "@anthropic/mcp-server-sequential-thinking", name: "sequential-thinking", args: [] },
|
|
729
|
-
];
|
|
730
|
-
for (const s of knownServers) {
|
|
731
|
-
try {
|
|
732
|
-
execSync(`npx --yes ${s.pkg} --help`, { timeout: 5000, stdio: "pipe", env: { ...process.env, PATH: process.env.PATH + ":/opt/homebrew/bin:/usr/local/bin" } });
|
|
733
|
-
discovered.push({ name: s.name, command: "npx", args: ["-y", s.pkg, ...s.args], source: "npm" });
|
|
734
|
-
}
|
|
735
|
-
catch {
|
|
736
|
-
// Not installed — try checking if globally available
|
|
737
|
-
try {
|
|
738
|
-
execSync(`npm list -g ${s.pkg} --depth=0`, { timeout: 5000, stdio: "pipe" });
|
|
739
|
-
discovered.push({ name: s.name, command: "npx", args: ["-y", s.pkg, ...s.args], source: "npm-global" });
|
|
740
|
-
}
|
|
741
|
-
catch { /* not installed */ }
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
// Check for Claude Desktop MCP config
|
|
745
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
746
|
-
const claudeConfigPaths = [
|
|
747
|
-
resolve(homeDir, ".config/claude/claude_desktop_config.json"),
|
|
748
|
-
resolve(homeDir, "Library/Application Support/Claude/claude_desktop_config.json"),
|
|
749
|
-
resolve(homeDir, "AppData/Roaming/Claude/claude_desktop_config.json"),
|
|
750
|
-
];
|
|
751
|
-
for (const cfgPath of claudeConfigPaths) {
|
|
752
|
-
try {
|
|
753
|
-
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
754
|
-
if (cfg.mcpServers) {
|
|
755
|
-
for (const [name, srv] of Object.entries(cfg.mcpServers)) {
|
|
756
|
-
if (srv.command) {
|
|
757
|
-
discovered.push({ name: `claude-${name}`, command: srv.command, args: srv.args || [], source: "claude-desktop" });
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
catch { /* not found */ }
|
|
763
|
-
}
|
|
764
|
-
res.end(JSON.stringify({ discovered }));
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
// ── Skills Management ─────────────────────────────────
|
|
768
|
-
// GET /api/skills — already in setup-api.ts, but add full CRUD here
|
|
769
|
-
// GET /api/skills/detail/:id — get full skill content
|
|
770
|
-
if (urlPath?.match(/^\/api\/skills\/detail\//) && req.method === "GET") {
|
|
771
|
-
const skillId = urlPath.split("/").pop();
|
|
772
|
-
const { getSkills } = await import("../services/skills.js");
|
|
773
|
-
const skill = getSkills().find(s => s.id === skillId);
|
|
774
|
-
if (skill) {
|
|
775
|
-
res.end(JSON.stringify({ ok: true, skill }));
|
|
776
|
-
}
|
|
777
|
-
else {
|
|
778
|
-
res.statusCode = 404;
|
|
779
|
-
res.end(JSON.stringify({ error: "Skill not found" }));
|
|
780
|
-
}
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
// POST /api/skills/create — create a new skill
|
|
784
|
-
if (urlPath === "/api/skills/create" && req.method === "POST") {
|
|
785
|
-
try {
|
|
786
|
-
const { id, name, description, triggers, category, content, priority } = JSON.parse(body);
|
|
787
|
-
if (!id || !name) {
|
|
788
|
-
res.statusCode = 400;
|
|
789
|
-
res.end(JSON.stringify({ error: "id and name required" }));
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
// Security: reject id values that could escape SKILLS_DIR.
|
|
793
|
-
// Mirror the resolve+startsWith containment pattern used in /api/files/save (server.ts ~:921).
|
|
794
|
-
// Also guard against ids with path separators or absolute paths before
|
|
795
|
-
// resolve() ever runs, so the raw string never reaches the filesystem.
|
|
796
|
-
if (typeof id !== "string" ||
|
|
797
|
-
id.includes("..") ||
|
|
798
|
-
id.includes("/") ||
|
|
799
|
-
id.includes("\\") ||
|
|
800
|
-
path.isAbsolute(id)) {
|
|
801
|
-
res.statusCode = 400;
|
|
802
|
-
res.end(JSON.stringify({ error: "Invalid skill id" }));
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
const skillsDir = SKILLS_DIR;
|
|
806
|
-
const skillDir = resolve(skillsDir, id);
|
|
807
|
-
// Belt-and-suspenders: resolved path must still be inside SKILLS_DIR
|
|
808
|
-
if (!skillDir.startsWith(skillsDir)) {
|
|
809
|
-
res.statusCode = 400;
|
|
810
|
-
res.end(JSON.stringify({ error: "Invalid skill id" }));
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
if (!fs.existsSync(skillDir))
|
|
814
|
-
fs.mkdirSync(skillDir, { recursive: true });
|
|
815
|
-
const frontmatter = [
|
|
816
|
-
"---",
|
|
817
|
-
`name: ${name}`,
|
|
818
|
-
description ? `description: ${description}` : "",
|
|
819
|
-
triggers ? `triggers: ${Array.isArray(triggers) ? triggers.join(", ") : triggers}` : "",
|
|
820
|
-
`priority: ${priority || 3}`,
|
|
821
|
-
`category: ${category || "custom"}`,
|
|
822
|
-
"---",
|
|
823
|
-
].filter(Boolean).join("\n");
|
|
824
|
-
fs.writeFileSync(resolve(skillDir, "SKILL.md"), `${frontmatter}\n\n${content || ""}`);
|
|
825
|
-
// Force reload
|
|
826
|
-
const { loadSkills } = await import("../services/skills.js");
|
|
827
|
-
loadSkills();
|
|
828
|
-
res.end(JSON.stringify({ ok: true }));
|
|
829
|
-
}
|
|
830
|
-
catch (e) {
|
|
831
|
-
res.statusCode = 400;
|
|
832
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
833
|
-
}
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
// POST /api/skills/update — update an existing skill
|
|
837
|
-
if (urlPath === "/api/skills/update" && req.method === "POST") {
|
|
838
|
-
try {
|
|
839
|
-
const { id, content } = JSON.parse(body);
|
|
840
|
-
// Security: same id containment as /api/skills/create
|
|
841
|
-
if (typeof id !== "string" ||
|
|
842
|
-
id.includes("..") ||
|
|
843
|
-
id.includes("/") ||
|
|
844
|
-
id.includes("\\") ||
|
|
845
|
-
path.isAbsolute(id)) {
|
|
846
|
-
res.statusCode = 400;
|
|
847
|
-
res.end(JSON.stringify({ error: "Invalid skill id" }));
|
|
848
|
-
return;
|
|
849
|
-
}
|
|
850
|
-
// Belt-and-suspenders: resolved path must still be inside SKILLS_DIR
|
|
851
|
-
if (!resolve(SKILLS_DIR, id).startsWith(SKILLS_DIR)) {
|
|
852
|
-
res.statusCode = 400;
|
|
853
|
-
res.end(JSON.stringify({ error: "Invalid skill id" }));
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
const skillPath = resolve(SKILLS_DIR, id, "SKILL.md");
|
|
857
|
-
if (!fs.existsSync(skillPath)) {
|
|
858
|
-
// Try flat file
|
|
859
|
-
const flatPath = resolve(SKILLS_DIR, id + ".md");
|
|
860
|
-
if (fs.existsSync(flatPath)) {
|
|
861
|
-
fs.writeFileSync(flatPath, content);
|
|
862
|
-
}
|
|
863
|
-
else {
|
|
864
|
-
res.statusCode = 404;
|
|
865
|
-
res.end(JSON.stringify({ error: "Skill not found" }));
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
else {
|
|
870
|
-
fs.writeFileSync(skillPath, content);
|
|
871
|
-
}
|
|
872
|
-
const { loadSkills } = await import("../services/skills.js");
|
|
873
|
-
loadSkills();
|
|
874
|
-
res.end(JSON.stringify({ ok: true }));
|
|
875
|
-
}
|
|
876
|
-
catch (e) {
|
|
877
|
-
res.statusCode = 400;
|
|
878
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
879
|
-
}
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
// POST /api/skills/delete — delete a skill
|
|
883
|
-
if (urlPath === "/api/skills/delete" && req.method === "POST") {
|
|
884
|
-
try {
|
|
885
|
-
const { id } = JSON.parse(body);
|
|
886
|
-
// Security: same raw-string id containment as /api/skills/create + /api/skills/update
|
|
887
|
-
if (typeof id !== "string" ||
|
|
888
|
-
id.includes("..") ||
|
|
889
|
-
id.includes("/") ||
|
|
890
|
-
id.includes("\\") ||
|
|
891
|
-
path.isAbsolute(id)) {
|
|
892
|
-
res.statusCode = 400;
|
|
893
|
-
res.end(JSON.stringify({ error: "Invalid skill id" }));
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
const skillDir = resolve(SKILLS_DIR, id);
|
|
897
|
-
const flatFile = resolve(SKILLS_DIR, id + ".md");
|
|
898
|
-
if (fs.existsSync(skillDir)) {
|
|
899
|
-
fs.rmSync(skillDir, { recursive: true });
|
|
900
|
-
}
|
|
901
|
-
else if (fs.existsSync(flatFile)) {
|
|
902
|
-
fs.unlinkSync(flatFile);
|
|
903
|
-
}
|
|
904
|
-
else {
|
|
905
|
-
res.statusCode = 404;
|
|
906
|
-
res.end(JSON.stringify({ error: "Skill not found" }));
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
const { loadSkills } = await import("../services/skills.js");
|
|
910
|
-
loadSkills();
|
|
911
|
-
res.end(JSON.stringify({ ok: true }));
|
|
912
|
-
}
|
|
913
|
-
catch (e) {
|
|
914
|
-
res.statusCode = 400;
|
|
915
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
916
|
-
}
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
// GET /api/config
|
|
920
|
-
if (urlPath === "/api/config") {
|
|
921
|
-
res.end(JSON.stringify({
|
|
922
|
-
providers: config.fallbackProviders,
|
|
923
|
-
primaryProvider: config.primaryProvider,
|
|
924
|
-
allowedUsers: config.allowedUsers,
|
|
925
|
-
hasKeys: {
|
|
926
|
-
groq: !!config.apiKeys.groq,
|
|
927
|
-
openai: !!config.apiKeys.openai,
|
|
928
|
-
google: !!config.apiKeys.google,
|
|
929
|
-
nvidia: !!config.apiKeys.nvidia,
|
|
930
|
-
openrouter: !!config.apiKeys.openrouter,
|
|
931
|
-
},
|
|
932
|
-
}));
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
// GET /api/sessions
|
|
936
|
-
if (urlPath === "/api/sessions") {
|
|
937
|
-
const sessions = getAllSessions();
|
|
938
|
-
const profiles = listProfiles();
|
|
939
|
-
const data = Array.from(sessions.entries()).map(([key, session]) => {
|
|
940
|
-
const userId = Number(key.split(":").pop());
|
|
941
|
-
const profile = profiles.find(p => p.userId === userId);
|
|
942
|
-
return {
|
|
943
|
-
userId: key,
|
|
944
|
-
name: profile?.name || `User ${key}`,
|
|
945
|
-
username: profile?.username,
|
|
946
|
-
messageCount: session.messageCount,
|
|
947
|
-
toolUseCount: session.toolUseCount,
|
|
948
|
-
totalCost: session.totalCost,
|
|
949
|
-
totalInputTokens: session.totalInputTokens || 0,
|
|
950
|
-
totalOutputTokens: session.totalOutputTokens || 0,
|
|
951
|
-
effort: session.effort,
|
|
952
|
-
startedAt: session.startedAt,
|
|
953
|
-
lastActivity: session.lastActivity,
|
|
954
|
-
historyLength: session.history.length,
|
|
955
|
-
isProcessing: session.isProcessing,
|
|
956
|
-
provider: Object.keys(session.queriesByProvider).join(", ") || "none",
|
|
957
|
-
};
|
|
958
|
-
});
|
|
959
|
-
res.end(JSON.stringify({ sessions: data }));
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
// GET /api/sessions/:userId/history
|
|
963
|
-
if (urlPath.match(/^\/api\/sessions\/\d+\/history$/)) {
|
|
964
|
-
const userId = parseInt(urlPath.split("/")[3]);
|
|
965
|
-
const session = getSession(userId);
|
|
966
|
-
res.end(JSON.stringify({
|
|
967
|
-
userId,
|
|
968
|
-
history: session.history.map(h => ({ role: h.role, content: h.content.slice(0, 2000) })),
|
|
969
|
-
}));
|
|
970
|
-
return;
|
|
971
|
-
}
|
|
972
|
-
// GET /api/files?path=...
|
|
973
|
-
if (urlPath === "/api/files") {
|
|
974
|
-
const params = new URLSearchParams((req.url || "").split("?")[1] || "");
|
|
975
|
-
const reqPath = params.get("path") || "";
|
|
976
|
-
const basePath = resolve(BOT_ROOT, reqPath || ".");
|
|
977
|
-
// Security: must be within BOT_ROOT
|
|
978
|
-
if (!basePath.startsWith(BOT_ROOT)) {
|
|
979
|
-
res.statusCode = 403;
|
|
980
|
-
res.end(JSON.stringify({ error: "Access denied" }));
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
try {
|
|
984
|
-
const stat = fs.statSync(basePath);
|
|
985
|
-
if (stat.isDirectory()) {
|
|
986
|
-
const entries = fs.readdirSync(basePath, { withFileTypes: true })
|
|
987
|
-
.filter(e => !e.name.startsWith(".") && e.name !== "node_modules")
|
|
988
|
-
.map(e => ({
|
|
989
|
-
name: e.name,
|
|
990
|
-
type: e.isDirectory() ? "dir" : "file",
|
|
991
|
-
size: e.isFile() ? fs.statSync(resolve(basePath, e.name)).size : 0,
|
|
992
|
-
modified: fs.statSync(resolve(basePath, e.name)).mtimeMs,
|
|
993
|
-
}))
|
|
994
|
-
.sort((a, b) => {
|
|
995
|
-
if (a.type !== b.type)
|
|
996
|
-
return a.type === "dir" ? -1 : 1;
|
|
997
|
-
return a.name.localeCompare(b.name);
|
|
998
|
-
});
|
|
999
|
-
res.end(JSON.stringify({ path: reqPath || ".", entries }));
|
|
1000
|
-
}
|
|
1001
|
-
else {
|
|
1002
|
-
// Read file content — text files up to 500KB
|
|
1003
|
-
const ext = path.extname(basePath).toLowerCase();
|
|
1004
|
-
const textExts = new Set([
|
|
1005
|
-
".md", ".txt", ".json", ".js", ".ts", ".jsx", ".tsx", ".css", ".html", ".htm",
|
|
1006
|
-
".xml", ".svg", ".yml", ".yaml", ".toml", ".ini", ".cfg", ".conf", ".env",
|
|
1007
|
-
".sh", ".bash", ".zsh", ".fish", ".py", ".rb", ".go", ".rs", ".java", ".kt",
|
|
1008
|
-
".c", ".cpp", ".h", ".hpp", ".cs", ".php", ".sql", ".graphql", ".prisma",
|
|
1009
|
-
".dockerfile", ".gitignore", ".gitattributes", ".editorconfig", ".prettierrc",
|
|
1010
|
-
".eslintrc", ".babelrc", ".npmrc", ".nvmrc", ".lock", ".log", ".csv", ".tsv",
|
|
1011
|
-
".mjs", ".cjs", ".mts", ".cts", ".vue", ".svelte", ".astro",
|
|
1012
|
-
]);
|
|
1013
|
-
// Files without extension that match known names are always text
|
|
1014
|
-
const textNames = new Set([
|
|
1015
|
-
"dockerfile", "makefile", "procfile", "gemfile", "rakefile",
|
|
1016
|
-
"vagrantfile", "brewfile", "justfile", "taskfile", "cakefile",
|
|
1017
|
-
"license", "licence", "readme", "changelog", "authors", "contributors",
|
|
1018
|
-
]);
|
|
1019
|
-
const baseName = path.basename(basePath).toLowerCase();
|
|
1020
|
-
const isKnownTextName = textNames.has(baseName);
|
|
1021
|
-
const isText = textExts.has(ext) || isKnownTextName || (!ext && stat.size < 100_000);
|
|
1022
|
-
if (stat.size > 500_000) {
|
|
1023
|
-
res.end(JSON.stringify({ path: reqPath, content: `[File too large: ${(stat.size / 1024).toFixed(1)} KB — max 500 KB]`, size: stat.size }));
|
|
1024
|
-
}
|
|
1025
|
-
else if (isText) {
|
|
1026
|
-
try {
|
|
1027
|
-
const content = fs.readFileSync(basePath, "utf-8");
|
|
1028
|
-
// Quick binary check: if >10% null bytes, it's binary
|
|
1029
|
-
const nullCount = [...content.slice(0, 1000)].filter(c => c === "\0").length;
|
|
1030
|
-
if (nullCount > 100) {
|
|
1031
|
-
res.end(JSON.stringify({ path: reqPath, content: null, size: stat.size, binary: true }));
|
|
1032
|
-
}
|
|
1033
|
-
else {
|
|
1034
|
-
res.end(JSON.stringify({ path: reqPath, content, size: stat.size }));
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
catch {
|
|
1038
|
-
res.end(JSON.stringify({ path: reqPath, content: null, size: stat.size, binary: true }));
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
else {
|
|
1042
|
-
res.end(JSON.stringify({ path: reqPath, content: null, size: stat.size, binary: true }));
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
catch {
|
|
1047
|
-
res.statusCode = 404;
|
|
1048
|
-
res.end(JSON.stringify({ error: "Not found" }));
|
|
1049
|
-
}
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
// POST /api/files/save
|
|
1053
|
-
if (urlPath === "/api/files/save" && req.method === "POST") {
|
|
1054
|
-
try {
|
|
1055
|
-
const { path: filePath, content } = JSON.parse(body);
|
|
1056
|
-
const absPath = resolve(BOT_ROOT, filePath);
|
|
1057
|
-
if (!absPath.startsWith(BOT_ROOT)) {
|
|
1058
|
-
res.statusCode = 403;
|
|
1059
|
-
res.end(JSON.stringify({ error: "Access denied" }));
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1062
|
-
fs.writeFileSync(absPath, content);
|
|
1063
|
-
res.end(JSON.stringify({ ok: true }));
|
|
1064
|
-
}
|
|
1065
|
-
catch (err) {
|
|
1066
|
-
res.statusCode = 400;
|
|
1067
|
-
const error = err instanceof Error ? err.message : "Invalid request";
|
|
1068
|
-
res.end(JSON.stringify({ error }));
|
|
1069
|
-
}
|
|
1070
|
-
return;
|
|
1071
|
-
}
|
|
1072
|
-
// POST /api/files/delete
|
|
1073
|
-
if (urlPath === "/api/files/delete" && req.method === "POST") {
|
|
1074
|
-
try {
|
|
1075
|
-
const { path: filePath } = JSON.parse(body);
|
|
1076
|
-
const absPath = resolve(BOT_ROOT, filePath);
|
|
1077
|
-
if (!absPath.startsWith(BOT_ROOT)) {
|
|
1078
|
-
res.statusCode = 403;
|
|
1079
|
-
res.end(JSON.stringify({ error: "Access denied" }));
|
|
1080
|
-
return;
|
|
1081
|
-
}
|
|
1082
|
-
// Safety: don't allow deleting critical files
|
|
1083
|
-
const critical = [".env", "package.json", "tsconfig.json", "ecosystem.config.cjs"];
|
|
1084
|
-
const baseName = path.basename(absPath);
|
|
1085
|
-
if (critical.includes(baseName)) {
|
|
1086
|
-
res.statusCode = 403;
|
|
1087
|
-
res.end(JSON.stringify({ error: `${baseName} cannot be deleted (protected)` }));
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
if (!fs.existsSync(absPath)) {
|
|
1091
|
-
res.statusCode = 404;
|
|
1092
|
-
res.end(JSON.stringify({ error: "File not found" }));
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
const stat = fs.statSync(absPath);
|
|
1096
|
-
if (stat.isDirectory()) {
|
|
1097
|
-
res.statusCode = 400;
|
|
1098
|
-
res.end(JSON.stringify({ error: "Directories cannot be deleted" }));
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
fs.unlinkSync(absPath);
|
|
1102
|
-
res.end(JSON.stringify({ ok: true }));
|
|
1103
|
-
}
|
|
1104
|
-
catch (err) {
|
|
1105
|
-
res.statusCode = 400;
|
|
1106
|
-
const error = err instanceof Error ? err.message : "Invalid request";
|
|
1107
|
-
res.end(JSON.stringify({ error }));
|
|
1108
|
-
}
|
|
1109
|
-
return;
|
|
1110
|
-
}
|
|
1111
|
-
// POST /api/terminal
|
|
1112
|
-
if (urlPath === "/api/terminal" && req.method === "POST") {
|
|
1113
|
-
try {
|
|
1114
|
-
const { command } = JSON.parse(body);
|
|
1115
|
-
if (!command) {
|
|
1116
|
-
res.statusCode = 400;
|
|
1117
|
-
res.end(JSON.stringify({ error: "No command" }));
|
|
1118
|
-
return;
|
|
1119
|
-
}
|
|
1120
|
-
// Security: limit command length
|
|
1121
|
-
if (command.length > 10000) {
|
|
1122
|
-
res.statusCode = 400;
|
|
1123
|
-
res.end(JSON.stringify({ error: "Command too long (max 10000 chars)" }));
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
const cwd = typeof (JSON.parse(body)).cwd === "string" ? resolve(JSON.parse(body).cwd) : BOT_ROOT;
|
|
1127
|
-
const output = execSync(command, {
|
|
1128
|
-
cwd,
|
|
1129
|
-
stdio: "pipe",
|
|
1130
|
-
timeout: 120000,
|
|
1131
|
-
env: { ...process.env, PATH: process.env.PATH + ":/opt/homebrew/bin:/usr/local/bin" },
|
|
1132
|
-
}).toString();
|
|
1133
|
-
res.end(JSON.stringify({ output: output.slice(0, 100000) }));
|
|
1134
|
-
}
|
|
1135
|
-
catch (err) {
|
|
1136
|
-
const error = err;
|
|
1137
|
-
const stderr = error.stderr?.toString()?.trim() || "";
|
|
1138
|
-
res.end(JSON.stringify({ output: stderr || error.message, exitCode: 1 }));
|
|
1139
|
-
}
|
|
1140
|
-
return;
|
|
1141
|
-
}
|
|
1142
|
-
// GET /api/env — read .env keys (names only, values masked)
|
|
1143
|
-
if (urlPath === "/api/env") {
|
|
1144
|
-
try {
|
|
1145
|
-
const envContent = fs.existsSync(ENV_FILE) ? fs.readFileSync(ENV_FILE, "utf-8") : "";
|
|
1146
|
-
const lines = envContent.split("\n").filter(l => l.includes("=") && !l.startsWith("#"));
|
|
1147
|
-
const vars = lines.map(l => {
|
|
1148
|
-
const [key, ...rest] = l.split("=");
|
|
1149
|
-
const value = rest.join("=").trim();
|
|
1150
|
-
// Mask sensitive values
|
|
1151
|
-
const masked = key.includes("KEY") || key.includes("TOKEN") || key.includes("PASSWORD") || key.includes("SECRET")
|
|
1152
|
-
? (value.length > 4 ? value.slice(0, 4) + "..." + value.slice(-4) : "****")
|
|
1153
|
-
: value;
|
|
1154
|
-
return { key: key.trim(), value: masked, hasValue: value.length > 0 };
|
|
1155
|
-
});
|
|
1156
|
-
res.end(JSON.stringify({ vars }));
|
|
1157
|
-
}
|
|
1158
|
-
catch {
|
|
1159
|
-
res.end(JSON.stringify({ vars: [] }));
|
|
1160
|
-
}
|
|
1161
|
-
return;
|
|
1162
|
-
}
|
|
1163
|
-
// POST /api/env/set — update an env var
|
|
1164
|
-
if (urlPath === "/api/env/set" && req.method === "POST") {
|
|
1165
|
-
try {
|
|
1166
|
-
const { key, value } = JSON.parse(body);
|
|
1167
|
-
if (!key || typeof key !== "string" || !key.match(/^[A-Z_][A-Z0-9_]*$/)) {
|
|
1168
|
-
res.statusCode = 400;
|
|
1169
|
-
res.end(JSON.stringify({ error: "Invalid key name" }));
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
|
-
// M6: reject values containing newline characters — they allow injecting
|
|
1173
|
-
// extra .env lines (e.g. value="good\nEVIL=injected").
|
|
1174
|
-
if (typeof value === "string" && /[\n\r]/.test(value)) {
|
|
1175
|
-
res.statusCode = 400;
|
|
1176
|
-
res.end(JSON.stringify({ error: "Invalid value: newline characters not allowed" }));
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
let envContent = fs.existsSync(ENV_FILE) ? fs.readFileSync(ENV_FILE, "utf-8") : "";
|
|
1180
|
-
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
1181
|
-
if (regex.test(envContent)) {
|
|
1182
|
-
envContent = envContent.replace(regex, `${key}=${value}`);
|
|
1183
|
-
}
|
|
1184
|
-
else {
|
|
1185
|
-
envContent = envContent.trimEnd() + `\n${key}=${value}\n`;
|
|
1186
|
-
}
|
|
1187
|
-
// v4.12.2 — enforce 0o600 on .env
|
|
1188
|
-
writeSecure(ENV_FILE, envContent);
|
|
1189
|
-
res.end(JSON.stringify({ ok: true, note: "Restart required for changes to take effect" }));
|
|
1190
|
-
}
|
|
1191
|
-
catch {
|
|
1192
|
-
res.statusCode = 400;
|
|
1193
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
1194
|
-
}
|
|
1195
|
-
return;
|
|
1196
|
-
}
|
|
1197
|
-
// GET /api/soul — read SOUL.md
|
|
1198
|
-
if (urlPath === "/api/soul") {
|
|
1199
|
-
const content = getSoulContent();
|
|
1200
|
-
res.end(JSON.stringify({ content }));
|
|
1201
|
-
return;
|
|
1202
|
-
}
|
|
1203
|
-
// POST /api/soul/save — update SOUL.md
|
|
1204
|
-
if (urlPath === "/api/soul/save" && req.method === "POST") {
|
|
1205
|
-
try {
|
|
1206
|
-
const { content } = JSON.parse(body);
|
|
1207
|
-
const soulPath = SOUL_FILE;
|
|
1208
|
-
fs.writeFileSync(soulPath, content);
|
|
1209
|
-
reloadSoul();
|
|
1210
|
-
res.end(JSON.stringify({ ok: true }));
|
|
1211
|
-
}
|
|
1212
|
-
catch {
|
|
1213
|
-
res.statusCode = 400;
|
|
1214
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
1215
|
-
}
|
|
1216
|
-
return;
|
|
1217
|
-
}
|
|
1218
|
-
// GET /api/platforms — platform adapter status
|
|
1219
|
-
if (urlPath === "/api/platforms") {
|
|
1220
|
-
const platforms = [
|
|
1221
|
-
{ name: "Telegram", key: "BOT_TOKEN", icon: "📱", configured: !!process.env.BOT_TOKEN },
|
|
1222
|
-
{ name: "Discord", key: "DISCORD_TOKEN", icon: "🎮", configured: !!process.env.DISCORD_TOKEN },
|
|
1223
|
-
{ name: "WhatsApp", key: "WHATSAPP_ENABLED", icon: "💬", configured: process.env.WHATSAPP_ENABLED === "true" },
|
|
1224
|
-
{ name: "Signal", key: "SIGNAL_API_URL", icon: "🔒", configured: !!process.env.SIGNAL_API_URL },
|
|
1225
|
-
{ name: "Web UI", key: "WEB_PORT", icon: "🌐", configured: true },
|
|
1226
|
-
];
|
|
1227
|
-
res.end(JSON.stringify({ platforms }));
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
// POST /api/restart — restart the bot process
|
|
1231
|
-
if (urlPath === "/api/restart" && req.method === "POST") {
|
|
1232
|
-
const { scheduleGracefulRestart } = await import("../services/restart.js");
|
|
1233
|
-
res.end(JSON.stringify({ ok: true, note: "Restarting..." }));
|
|
1234
|
-
scheduleGracefulRestart(500);
|
|
1235
|
-
return;
|
|
1236
|
-
}
|
|
1237
|
-
// POST /api/chat/export — export chat history
|
|
1238
|
-
if (urlPath === "/api/chat/export" && req.method === "POST") {
|
|
1239
|
-
try {
|
|
1240
|
-
const { messages, format } = JSON.parse(body);
|
|
1241
|
-
if (format === "json") {
|
|
1242
|
-
res.setHeader("Content-Type", "application/json");
|
|
1243
|
-
res.end(JSON.stringify({ export: messages }, null, 2));
|
|
1244
|
-
}
|
|
1245
|
-
else {
|
|
1246
|
-
// Markdown
|
|
1247
|
-
const md = messages.map((m) => {
|
|
1248
|
-
const prefix = m.role === "user" ? "**Du:**" : m.role === "assistant" ? "**Alvin Bot:**" : "*System:*";
|
|
1249
|
-
const time = m.time ? ` _(${m.time})_` : "";
|
|
1250
|
-
return `${prefix}${time}\n${m.text}\n`;
|
|
1251
|
-
}).join("\n---\n\n");
|
|
1252
|
-
res.setHeader("Content-Type", "text/markdown");
|
|
1253
|
-
res.end(`# Chat Export — Alvin Bot\n_${new Date().toLocaleString("de-DE")}_\n\n---\n\n${md}`);
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
catch {
|
|
1257
|
-
res.statusCode = 400;
|
|
1258
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
1259
|
-
}
|
|
1260
|
-
return;
|
|
1261
|
-
}
|
|
1262
|
-
// ── WhatsApp Group Management API ────────────────────────────────────
|
|
1263
|
-
// GET /api/whatsapp/groups — list all WhatsApp groups (live from WA)
|
|
1264
|
-
if (urlPath === "/api/whatsapp/groups" && req.method === "GET") {
|
|
1265
|
-
try {
|
|
1266
|
-
const { getWhatsAppAdapter } = await import("../platforms/whatsapp.js");
|
|
1267
|
-
const adapter = getWhatsAppAdapter();
|
|
1268
|
-
if (!adapter) {
|
|
1269
|
-
res.end(JSON.stringify({ groups: [], error: "WhatsApp nicht verbunden" }));
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
const groups = await adapter.getGroups();
|
|
1273
|
-
res.end(JSON.stringify({ groups }));
|
|
1274
|
-
}
|
|
1275
|
-
catch (err) {
|
|
1276
|
-
res.end(JSON.stringify({ groups: [], error: String(err) }));
|
|
1277
|
-
}
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
// GET /api/whatsapp/groups/:id/participants — fetch group participants
|
|
1281
|
-
if (urlPath.match(/^\/api\/whatsapp\/groups\/[^/]+\/participants$/)) {
|
|
1282
|
-
try {
|
|
1283
|
-
const groupId = decodeURIComponent(urlPath.split("/")[4]);
|
|
1284
|
-
const { getWhatsAppAdapter } = await import("../platforms/whatsapp.js");
|
|
1285
|
-
const adapter = getWhatsAppAdapter();
|
|
1286
|
-
if (!adapter) {
|
|
1287
|
-
res.end(JSON.stringify({ participants: [], error: "WhatsApp nicht verbunden" }));
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1290
|
-
const participants = await adapter.getGroupParticipants(groupId);
|
|
1291
|
-
res.end(JSON.stringify({ participants }));
|
|
1292
|
-
}
|
|
1293
|
-
catch (err) {
|
|
1294
|
-
res.end(JSON.stringify({ participants: [], error: String(err) }));
|
|
1295
|
-
}
|
|
1296
|
-
return;
|
|
1297
|
-
}
|
|
1298
|
-
// GET /api/whatsapp/group-rules — get all configured group rules
|
|
1299
|
-
if (urlPath === "/api/whatsapp/group-rules" && req.method === "GET") {
|
|
1300
|
-
const { getGroupRules } = await import("../platforms/whatsapp.js");
|
|
1301
|
-
res.end(JSON.stringify({ rules: getGroupRules() }));
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
// POST /api/whatsapp/group-rules — create or update a group rule
|
|
1305
|
-
if (urlPath === "/api/whatsapp/group-rules" && req.method === "POST") {
|
|
1306
|
-
try {
|
|
1307
|
-
const rule = JSON.parse(body);
|
|
1308
|
-
if (!rule.groupId) {
|
|
1309
|
-
res.statusCode = 400;
|
|
1310
|
-
res.end(JSON.stringify({ error: "groupId ist erforderlich" }));
|
|
1311
|
-
return;
|
|
1312
|
-
}
|
|
1313
|
-
const { upsertGroupRule } = await import("../platforms/whatsapp.js");
|
|
1314
|
-
const saved = upsertGroupRule(rule);
|
|
1315
|
-
res.end(JSON.stringify({ ok: true, rule: saved }));
|
|
1316
|
-
}
|
|
1317
|
-
catch (err) {
|
|
1318
|
-
res.statusCode = 400;
|
|
1319
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
1320
|
-
}
|
|
1321
|
-
return;
|
|
1322
|
-
}
|
|
1323
|
-
// DELETE /api/whatsapp/group-rules/:id — delete a group rule
|
|
1324
|
-
if (urlPath.match(/^\/api\/whatsapp\/group-rules\//) && req.method === "DELETE") {
|
|
1325
|
-
// Pattern route — gate here since EXPOSED_DANGEROUS_ROUTES can't express :id
|
|
1326
|
-
if (isExposedWithoutPassword()) {
|
|
1327
|
-
res.statusCode = 403;
|
|
1328
|
-
res.end(JSON.stringify({ error: "refused: web exposed without WEB_PASSWORD" }));
|
|
1329
|
-
return;
|
|
1330
|
-
}
|
|
1331
|
-
const groupId = decodeURIComponent(urlPath.split("/").slice(4).join("/"));
|
|
1332
|
-
const { deleteGroupRule } = await import("../platforms/whatsapp.js");
|
|
1333
|
-
const ok = deleteGroupRule(groupId);
|
|
1334
|
-
res.end(JSON.stringify({ ok }));
|
|
1335
|
-
return;
|
|
1336
|
-
}
|
|
1337
|
-
res.statusCode = 404;
|
|
1338
|
-
res.end(JSON.stringify({ error: "Not found" }));
|
|
1339
|
-
}
|
|
1340
|
-
// ── WebSocket Chat ──────────────────────────────────────
|
|
1341
|
-
// Set of all currently connected chat WebSocket clients (excluding canvas).
|
|
1342
|
-
// Populated on connect, cleaned up on close. Used to forward Telegram
|
|
1343
|
-
// activity to every observer.
|
|
1344
|
-
const chatClients = new Set();
|
|
1345
|
-
/**
|
|
1346
|
-
* Wire the broadcast bus once at module load. The bus is singleton, so
|
|
1347
|
-
* subscribing here means every Telegram message fan-outs to every connected
|
|
1348
|
-
* chat client — without any per-connection re-subscription.
|
|
1349
|
-
*/
|
|
1350
|
-
broadcast.on("user_msg", (payload) => {
|
|
1351
|
-
if (payload.platform !== "telegram")
|
|
1352
|
-
return; // v4.5.0: telegram only for now
|
|
1353
|
-
const json = JSON.stringify({
|
|
1354
|
-
type: "mirror:user_msg",
|
|
1355
|
-
text: payload.text,
|
|
1356
|
-
platform: payload.platform,
|
|
1357
|
-
userName: payload.userName,
|
|
1358
|
-
ts: payload.ts,
|
|
1359
|
-
});
|
|
1360
|
-
for (const client of chatClients) {
|
|
1361
|
-
if (client.readyState === WebSocket.OPEN)
|
|
1362
|
-
client.send(json);
|
|
1363
|
-
}
|
|
1364
|
-
});
|
|
1365
|
-
broadcast.on("response_start", (payload) => {
|
|
1366
|
-
if (payload.platform !== "telegram")
|
|
1367
|
-
return;
|
|
1368
|
-
const json = JSON.stringify({ type: "mirror:response_start", platform: payload.platform, ts: payload.ts });
|
|
1369
|
-
for (const client of chatClients) {
|
|
1370
|
-
if (client.readyState === WebSocket.OPEN)
|
|
1371
|
-
client.send(json);
|
|
1372
|
-
}
|
|
1373
|
-
});
|
|
1374
|
-
broadcast.on("response_delta", (payload) => {
|
|
1375
|
-
if (payload.platform !== "telegram")
|
|
1376
|
-
return;
|
|
1377
|
-
const json = JSON.stringify({ type: "mirror:response_delta", delta: payload.delta, platform: payload.platform, ts: payload.ts });
|
|
1378
|
-
for (const client of chatClients) {
|
|
1379
|
-
if (client.readyState === WebSocket.OPEN)
|
|
1380
|
-
client.send(json);
|
|
1381
|
-
}
|
|
1382
|
-
});
|
|
1383
|
-
broadcast.on("response_done", (payload) => {
|
|
1384
|
-
if (payload.platform !== "telegram")
|
|
1385
|
-
return;
|
|
1386
|
-
const json = JSON.stringify({ type: "mirror:response_done", cost: payload.cost, platform: payload.platform, ts: payload.ts });
|
|
1387
|
-
for (const client of chatClients) {
|
|
1388
|
-
if (client.readyState === WebSocket.OPEN)
|
|
1389
|
-
client.send(json);
|
|
1390
|
-
}
|
|
1391
|
-
});
|
|
1392
|
-
function handleWebSocket(wss) {
|
|
1393
|
-
wss.on("connection", (ws, req) => {
|
|
1394
|
-
// Auth check
|
|
1395
|
-
if (WEB_PASSWORD && !checkAuth(req)) {
|
|
1396
|
-
ws.close(4001, "Not authenticated");
|
|
1397
|
-
return;
|
|
1398
|
-
}
|
|
1399
|
-
// Canvas WebSocket — separate handler
|
|
1400
|
-
const wsUrl = req.url || "/";
|
|
1401
|
-
if (wsUrl === "/canvas/ws") {
|
|
1402
|
-
addCanvasClient(ws);
|
|
1403
|
-
return;
|
|
1404
|
-
}
|
|
1405
|
-
console.log("WebUI: client connected");
|
|
1406
|
-
chatClients.add(ws);
|
|
1407
|
-
ws.on("message", async (data) => {
|
|
1408
|
-
try {
|
|
1409
|
-
const msg = JSON.parse(data.toString());
|
|
1410
|
-
if (msg.type === "chat") {
|
|
1411
|
-
let { text, effort, file } = msg;
|
|
1412
|
-
// v4.5.0: session routing. The client (TUI/WebUI) tells us which
|
|
1413
|
-
// session it wants its message to go into. Supported targets:
|
|
1414
|
-
// - "tui" → use msg.sessionKey (e.g. "tui:local" or
|
|
1415
|
-
// "tui:ephemeral:…"). Isolated from Telegram.
|
|
1416
|
-
// - "telegram" → route into the primary Telegram user's session.
|
|
1417
|
-
// Responses go back to the client AND to the
|
|
1418
|
-
// actual Telegram chat via the broadcast bus.
|
|
1419
|
-
// - undefined → backwards-compatible: default to the primary
|
|
1420
|
-
// allowed user's session (old behavior).
|
|
1421
|
-
const target = msg.target;
|
|
1422
|
-
const telegramUserId = config.allowedUsers[0] || 0;
|
|
1423
|
-
let sessionKey;
|
|
1424
|
-
if (target === "tui" && typeof msg.sessionKey === "string" && msg.sessionKey.startsWith("tui:")) {
|
|
1425
|
-
sessionKey = msg.sessionKey;
|
|
1426
|
-
}
|
|
1427
|
-
else if (target === "telegram") {
|
|
1428
|
-
sessionKey = telegramUserId;
|
|
1429
|
-
}
|
|
1430
|
-
else {
|
|
1431
|
-
sessionKey = telegramUserId; // backwards compat
|
|
1432
|
-
}
|
|
1433
|
-
// Handle file upload — save to temp and reference in prompt
|
|
1434
|
-
if (file?.dataUrl && file?.name) {
|
|
1435
|
-
try {
|
|
1436
|
-
const dataDir = resolve(DATA_DIR, "web-uploads");
|
|
1437
|
-
if (!fs.existsSync(dataDir))
|
|
1438
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
1439
|
-
const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1440
|
-
const filePath = resolve(dataDir, `${Date.now()}_${safeName}`);
|
|
1441
|
-
const base64Data = file.dataUrl.split(",")[1] || file.dataUrl;
|
|
1442
|
-
fs.writeFileSync(filePath, Buffer.from(base64Data, "base64"));
|
|
1443
|
-
// Replace placeholder with actual file path
|
|
1444
|
-
text = text.replace(/\[File attached:.*?\]/, `[File saved: ${filePath}]`);
|
|
1445
|
-
}
|
|
1446
|
-
catch (err) {
|
|
1447
|
-
console.error("WebUI file upload error:", err);
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
const registry = getRegistry();
|
|
1451
|
-
const activeProvider = registry.getActive();
|
|
1452
|
-
const isSDK = activeProvider.config.type === "claude-sdk";
|
|
1453
|
-
const session = getSession(sessionKey);
|
|
1454
|
-
const queryOpts = {
|
|
1455
|
-
prompt: text,
|
|
1456
|
-
systemPrompt: buildSystemPrompt(isSDK, session.language, target === "telegram" ? "telegram" : "web-dashboard"),
|
|
1457
|
-
workingDir: session.workingDir,
|
|
1458
|
-
effort: effort || session.effort,
|
|
1459
|
-
sessionId: isSDK ? session.sessionId : null,
|
|
1460
|
-
history: !isSDK ? session.history : undefined,
|
|
1461
|
-
};
|
|
1462
|
-
let gotDone = false;
|
|
1463
|
-
let finalText = ""; // v4.5.0: capture the final response for target=telegram relay
|
|
1464
|
-
try {
|
|
1465
|
-
// Stream response
|
|
1466
|
-
for await (const chunk of registry.queryWithFallback(queryOpts)) {
|
|
1467
|
-
if (ws.readyState !== WebSocket.OPEN)
|
|
1468
|
-
break;
|
|
1469
|
-
switch (chunk.type) {
|
|
1470
|
-
case "text":
|
|
1471
|
-
if (chunk.text)
|
|
1472
|
-
finalText = chunk.text;
|
|
1473
|
-
ws.send(JSON.stringify({ type: "text", text: chunk.text, delta: chunk.delta }));
|
|
1474
|
-
break;
|
|
1475
|
-
case "tool_use":
|
|
1476
|
-
ws.send(JSON.stringify({ type: "tool", name: chunk.toolName, input: chunk.toolInput }));
|
|
1477
|
-
break;
|
|
1478
|
-
case "done":
|
|
1479
|
-
gotDone = true;
|
|
1480
|
-
if (chunk.text)
|
|
1481
|
-
finalText = chunk.text;
|
|
1482
|
-
if (chunk.sessionId)
|
|
1483
|
-
session.sessionId = chunk.sessionId;
|
|
1484
|
-
if (chunk.costUsd)
|
|
1485
|
-
session.totalCost += chunk.costUsd;
|
|
1486
|
-
if (chunk.inputTokens)
|
|
1487
|
-
session.totalInputTokens = (session.totalInputTokens || 0) + chunk.inputTokens;
|
|
1488
|
-
if (chunk.outputTokens)
|
|
1489
|
-
session.totalOutputTokens = (session.totalOutputTokens || 0) + chunk.outputTokens;
|
|
1490
|
-
ws.send(JSON.stringify({
|
|
1491
|
-
type: "done", cost: chunk.costUsd, sessionId: chunk.sessionId,
|
|
1492
|
-
inputTokens: chunk.inputTokens, outputTokens: chunk.outputTokens,
|
|
1493
|
-
sessionTokens: { input: session.totalInputTokens || 0, output: session.totalOutputTokens || 0 },
|
|
1494
|
-
}));
|
|
1495
|
-
break;
|
|
1496
|
-
case "error":
|
|
1497
|
-
ws.send(JSON.stringify({ type: "error", error: chunk.error }));
|
|
1498
|
-
gotDone = true; // error counts as done
|
|
1499
|
-
break;
|
|
1500
|
-
case "fallback":
|
|
1501
|
-
ws.send(JSON.stringify({ type: "fallback", from: chunk.failedProvider, to: chunk.providerName }));
|
|
1502
|
-
break;
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
// Ensure we always send done (in case stream ended without done/error chunk)
|
|
1506
|
-
if (!gotDone && ws.readyState === WebSocket.OPEN) {
|
|
1507
|
-
ws.send(JSON.stringify({ type: "done", cost: 0 }));
|
|
1508
|
-
}
|
|
1509
|
-
// v4.5.0: if the user typed in the TUI with target=telegram, we
|
|
1510
|
-
// must also post the bot's final response to the actual Telegram
|
|
1511
|
-
// chat so the continuity is preserved from the Telegram side.
|
|
1512
|
-
// (Telegram bots cannot forge user messages, so only the
|
|
1513
|
-
// response lands in the chat — the user prompt itself stays
|
|
1514
|
-
// in the TUI.)
|
|
1515
|
-
if (target === "telegram" && finalText.trim()) {
|
|
1516
|
-
try {
|
|
1517
|
-
const dq = await import("../services/delivery-queue.js");
|
|
1518
|
-
dq.enqueue("telegram", String(telegramUserId), finalText);
|
|
1519
|
-
}
|
|
1520
|
-
catch (err) {
|
|
1521
|
-
console.error("WebUI → Telegram relay failed:", err);
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
catch (streamErr) {
|
|
1526
|
-
const errMsg = streamErr instanceof Error ? streamErr.message : String(streamErr);
|
|
1527
|
-
console.error("WebUI stream error:", errMsg);
|
|
1528
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
1529
|
-
ws.send(JSON.stringify({ type: "error", error: errMsg }));
|
|
1530
|
-
if (!gotDone) {
|
|
1531
|
-
ws.send(JSON.stringify({ type: "done", cost: 0 }));
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
if (msg.type === "reset") {
|
|
1537
|
-
// v4.5.0: reset the target session, not a hardcoded one.
|
|
1538
|
-
const target = msg.target;
|
|
1539
|
-
const telegramUserId = config.allowedUsers[0] || 0;
|
|
1540
|
-
let resetKey;
|
|
1541
|
-
if (target === "tui" && typeof msg.sessionKey === "string" && msg.sessionKey.startsWith("tui:")) {
|
|
1542
|
-
resetKey = msg.sessionKey;
|
|
1543
|
-
}
|
|
1544
|
-
else {
|
|
1545
|
-
resetKey = telegramUserId;
|
|
1546
|
-
}
|
|
1547
|
-
resetSession(resetKey);
|
|
1548
|
-
ws.send(JSON.stringify({ type: "reset", ok: true }));
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
catch (err) {
|
|
1552
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
1553
|
-
ws.send(JSON.stringify({ type: "error", error }));
|
|
1554
|
-
}
|
|
1555
|
-
});
|
|
1556
|
-
ws.on("close", () => {
|
|
1557
|
-
console.log("WebUI: client disconnected");
|
|
1558
|
-
chatClients.delete(ws);
|
|
1559
|
-
});
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
|
-
// ── Start Server ────────────────────────────────────────
|
|
1563
|
-
/**
|
|
1564
|
-
* HTTP request handler for the web UI. Hoisted to a top-level function
|
|
1565
|
-
* so every bind attempt can create a fresh http.Server without
|
|
1566
|
-
* rebuilding the handler closure.
|
|
1567
|
-
*/
|
|
1568
|
-
function handleWebRequest(req, res) {
|
|
1569
|
-
let body = "";
|
|
1570
|
-
req.on("data", (chunk) => { body += chunk; });
|
|
1571
|
-
req.on("end", () => {
|
|
1572
|
-
const urlPath = (req.url || "/").split("?")[0];
|
|
1573
|
-
// OpenAI-compatible API (/v1/chat/completions, /v1/models)
|
|
1574
|
-
if (urlPath.startsWith("/v1/")) {
|
|
1575
|
-
handleOpenAICompat(req, res, urlPath, body);
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1578
|
-
// API routes
|
|
1579
|
-
if (urlPath.startsWith("/api/")) {
|
|
1580
|
-
handleAPI(req, res, urlPath, body);
|
|
1581
|
-
return;
|
|
1582
|
-
}
|
|
1583
|
-
// Auth page (if password set and not authenticated)
|
|
1584
|
-
if (WEB_PASSWORD && !checkAuth(req) && urlPath !== "/login.html") {
|
|
1585
|
-
res.writeHead(302, { Location: "/login.html" });
|
|
1586
|
-
res.end();
|
|
1587
|
-
return;
|
|
1588
|
-
}
|
|
1589
|
-
// Canvas UI
|
|
1590
|
-
if (urlPath === "/canvas") {
|
|
1591
|
-
const canvasFile = resolve(PUBLIC_DIR, "canvas.html");
|
|
1592
|
-
try {
|
|
1593
|
-
const content = fs.readFileSync(canvasFile);
|
|
1594
|
-
res.setHeader("Content-Type", "text/html");
|
|
1595
|
-
res.end(content);
|
|
1596
|
-
}
|
|
1597
|
-
catch {
|
|
1598
|
-
res.statusCode = 404;
|
|
1599
|
-
res.end("Not found");
|
|
1600
|
-
}
|
|
1601
|
-
return;
|
|
1602
|
-
}
|
|
1603
|
-
// Static files
|
|
1604
|
-
let filePath = urlPath === "/" ? "/index.html" : urlPath;
|
|
1605
|
-
filePath = resolve(PUBLIC_DIR, filePath.slice(1));
|
|
1606
|
-
// Security: prevent path traversal
|
|
1607
|
-
if (!filePath.startsWith(PUBLIC_DIR)) {
|
|
1608
|
-
res.statusCode = 403;
|
|
1609
|
-
res.end("Forbidden");
|
|
1610
|
-
return;
|
|
1611
|
-
}
|
|
1612
|
-
try {
|
|
1613
|
-
const content = fs.readFileSync(filePath);
|
|
1614
|
-
const ext = path.extname(filePath);
|
|
1615
|
-
res.setHeader("Content-Type", MIME[ext] || "application/octet-stream");
|
|
1616
|
-
res.end(content);
|
|
1617
|
-
}
|
|
1618
|
-
catch {
|
|
1619
|
-
res.statusCode = 404;
|
|
1620
|
-
res.end("Not found");
|
|
1621
|
-
}
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
1624
|
-
/**
|
|
1625
|
-
* Kick off the web-UI bind loop. NEVER throws, NEVER blocks.
|
|
1626
|
-
*
|
|
1627
|
-
* History: earlier versions returned an http.Server synchronously and
|
|
1628
|
-
* let listen() errors bubble up as uncaught exceptions — a colleague
|
|
1629
|
-
* flagged this on 2026-04-13 after spending months fighting the exact
|
|
1630
|
-
* same bug on a parallel OpenClaw fork. Their resolution: "the gateway
|
|
1631
|
-
* is a feature, not core. Decouple it."
|
|
1632
|
-
*
|
|
1633
|
-
* New contract:
|
|
1634
|
-
* - Returns `void` immediately. The actual bind happens asynchronously.
|
|
1635
|
-
* - If port 3100 is busy, tries 3101…3119 in sequence (same as before).
|
|
1636
|
-
* - If ALL 20 ports are busy, schedules a background retry at 3100
|
|
1637
|
-
* in `BACKGROUND_RETRY_MS` — keeps trying forever until success
|
|
1638
|
-
* or stopWebServer() is called.
|
|
1639
|
-
* - Any non-EADDRINUSE error also falls through to background retry.
|
|
1640
|
-
* - Each attempt uses a FRESH http.Server to avoid node's fragile
|
|
1641
|
-
* "listen-called-twice" state-recycling behaviour.
|
|
1642
|
-
* - The main Telegram bot is completely independent of this — if the
|
|
1643
|
-
* web UI never binds, the bot still answers messages.
|
|
1644
|
-
*/
|
|
1645
|
-
export function startWebServer() {
|
|
1646
|
-
stopRequested = false;
|
|
1647
|
-
scheduleBindAttempt(parseInt(process.env.WEB_PORT || "3100", 10), 0);
|
|
1648
|
-
}
|
|
1649
|
-
function scheduleBindAttempt(port, attempt) {
|
|
1650
|
-
if (stopRequested)
|
|
1651
|
-
return;
|
|
1652
|
-
// Read WEB_PORT live every time rather than closing over the
|
|
1653
|
-
// module-load value, so tests that change process.env.WEB_PORT
|
|
1654
|
-
// between runs see the new port.
|
|
1655
|
-
const originalPort = parseInt(process.env.WEB_PORT || "3100");
|
|
1656
|
-
// Fresh server for each attempt. Recycling a server that has already
|
|
1657
|
-
// emitted an EADDRINUSE error has produced "Listen method has been
|
|
1658
|
-
// called more than once" crashes in the wild.
|
|
1659
|
-
//
|
|
1660
|
-
// IMPORTANT: do NOT attach the WebSocketServer yet. The `ws` library
|
|
1661
|
-
// installs its own event plumbing on the http.Server in its
|
|
1662
|
-
// constructor, which causes bind errors to escape as uncaught
|
|
1663
|
-
// exceptions. We only attach it AFTER listen() has succeeded.
|
|
1664
|
-
const server = http.createServer(handleWebRequest);
|
|
1665
|
-
// Double-invocation guard: on some Node versions `server.listen`
|
|
1666
|
-
// both throws synchronously AND emits an `error` event for the same
|
|
1667
|
-
// bind failure. Without the guard we'd climb the ladder twice in
|
|
1668
|
-
// parallel and end up with two retry cascades racing each other.
|
|
1669
|
-
let handled = false;
|
|
1670
|
-
const cleanupDeadAttempt = () => {
|
|
1671
|
-
try {
|
|
1672
|
-
server.removeAllListeners("error");
|
|
1673
|
-
}
|
|
1674
|
-
catch { /* ignore */ }
|
|
1675
|
-
try {
|
|
1676
|
-
server.close(() => { });
|
|
1677
|
-
}
|
|
1678
|
-
catch { /* ignore */ }
|
|
1679
|
-
};
|
|
1680
|
-
const handleBindFailure = (err) => {
|
|
1681
|
-
if (handled)
|
|
1682
|
-
return;
|
|
1683
|
-
handled = true;
|
|
1684
|
-
cleanupDeadAttempt();
|
|
1685
|
-
if (stopRequested)
|
|
1686
|
-
return;
|
|
1687
|
-
const action = decideNextBindAction(err, attempt, {
|
|
1688
|
-
originalPort,
|
|
1689
|
-
maxPortTries: MAX_PORT_TRIES,
|
|
1690
|
-
backgroundRetryMs: BACKGROUND_RETRY_MS,
|
|
1691
|
-
});
|
|
1692
|
-
if (action.type === "retry-port") {
|
|
1693
|
-
console.warn(`[web] port ${port} busy (${err.code || err.message}) — trying ${action.port}`);
|
|
1694
|
-
scheduleBindAttempt(action.port, action.attempt);
|
|
1695
|
-
return;
|
|
1696
|
-
}
|
|
1697
|
-
// action.type === "retry-background"
|
|
1698
|
-
console.warn(`[web] bind failed (${err.code || err.message}) — ` +
|
|
1699
|
-
`backing off ${action.delayMs / 1000}s then retrying port ${action.port}. ` +
|
|
1700
|
-
`Bot is unaffected; Telegram remains live.`);
|
|
1701
|
-
bindRetryTimer = setTimeout(() => {
|
|
1702
|
-
bindRetryTimer = null;
|
|
1703
|
-
scheduleBindAttempt(action.port, 0);
|
|
1704
|
-
}, action.delayMs);
|
|
1705
|
-
};
|
|
1706
|
-
// Use `on` (not `once`) so a pathological server that emits two
|
|
1707
|
-
// error events for a single failure doesn't leave the second one
|
|
1708
|
-
// uncaught. The `handled` guard makes the handler idempotent.
|
|
1709
|
-
server.on("error", handleBindFailure);
|
|
1710
|
-
// Defensive try/catch — `server.listen()` usually emits async errors,
|
|
1711
|
-
// but certain Node versions + edge cases (already-listening server,
|
|
1712
|
-
// invalid backlog, kernel hiccup) can throw synchronously. Catch here
|
|
1713
|
-
// so the main routine never crashes during web-UI bind.
|
|
1714
|
-
try {
|
|
1715
|
-
// v4.20.2 — bind to config.webHost (default 127.0.0.1) so the Web UI
|
|
1716
|
-
// is loopback-only unless the operator opts in by setting WEB_HOST=0.0.0.0.
|
|
1717
|
-
// Empty/"*" maps to all interfaces.
|
|
1718
|
-
const bindHost = (config.webHost === "*" || config.webHost === "") ? undefined : config.webHost;
|
|
1719
|
-
server.listen(port, bindHost, () => {
|
|
1720
|
-
if (handled)
|
|
1721
|
-
return; // Should be impossible; paranoia.
|
|
1722
|
-
handled = true;
|
|
1723
|
-
// Now — and only now — attach the WebSocketServer. Before the
|
|
1724
|
-
// bind succeeded, the ws library's constructor would hijack the
|
|
1725
|
-
// http.Server's error event chain and let EADDRINUSE escape as
|
|
1726
|
-
// uncaught. Post-bind is safe.
|
|
1727
|
-
const wss = new WebSocketServer({ server });
|
|
1728
|
-
handleWebSocket(wss);
|
|
1729
|
-
currentServer = server;
|
|
1730
|
-
wsServerRef = wss;
|
|
1731
|
-
actualWebPort = port;
|
|
1732
|
-
// Remove the bind error handler — post-listen errors (socket
|
|
1733
|
-
// errors, close events) should not kick off a spurious retry
|
|
1734
|
-
// cycle. Install a quiet logger for any stray error events so
|
|
1735
|
-
// they can't escape as uncaught.
|
|
1736
|
-
server.removeListener("error", handleBindFailure);
|
|
1737
|
-
server.on("error", (err) => {
|
|
1738
|
-
console.warn(`[web] post-bind server error (ignored): ${err.message}`);
|
|
1739
|
-
});
|
|
1740
|
-
const bindLabel = bindHost && bindHost !== "127.0.0.1" && bindHost !== "::1"
|
|
1741
|
-
? `http://${bindHost}:${actualWebPort}` + (bindHost === "0.0.0.0" ? " (LAN-reachable)" : "")
|
|
1742
|
-
: `http://localhost:${actualWebPort}`;
|
|
1743
|
-
console.log(`🌐 Web UI: ${bindLabel}`);
|
|
1744
|
-
if (actualWebPort !== originalPort) {
|
|
1745
|
-
console.log(` (Port ${originalPort} was busy, using ${actualWebPort} instead)`);
|
|
1746
|
-
}
|
|
1747
|
-
if (isExposedWithoutPassword()) {
|
|
1748
|
-
// Upgrade from warn to a clear one-time log: the hard gate (above in
|
|
1749
|
-
// handleAPI) already blocks every mutating/exec route in this state,
|
|
1750
|
-
// so this message explains why those calls are being refused.
|
|
1751
|
-
console.log("[web] Non-loopback host with no WEB_PASSWORD: mutating/exec endpoints are disabled " +
|
|
1752
|
-
"(hard-refused 403). Set WEB_PASSWORD in ~/.alvin-bot/.env to unlock them, " +
|
|
1753
|
-
"or restrict to loopback with WEB_HOST=127.0.0.1.");
|
|
1754
|
-
}
|
|
1755
|
-
});
|
|
1756
|
-
}
|
|
1757
|
-
catch (err) {
|
|
1758
|
-
handleBindFailure(err);
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
/**
|
|
1762
|
-
* Gracefully close a specific http.Server — the low-level building
|
|
1763
|
-
* block. Exported for tests and for any future callers that manage
|
|
1764
|
-
* their own servers. Production bot code uses `stopWebServer()` below
|
|
1765
|
-
* which operates on the module-global current server instead.
|
|
1766
|
-
*
|
|
1767
|
-
* What this does:
|
|
1768
|
-
* 1. Force-close idle keep-alive sockets (Node 18.2+).
|
|
1769
|
-
* 2. Force-close active open requests (long-poll clients).
|
|
1770
|
-
* 3. Await `server.close()` so the listening socket is truly freed.
|
|
1771
|
-
*
|
|
1772
|
-
* Safe to call on already-closed, never-listened, or mid-listen servers.
|
|
1773
|
-
* Never throws.
|
|
1774
|
-
*/
|
|
1775
|
-
export async function closeHttpServerGracefully(server) {
|
|
1776
|
-
if (!server.listening)
|
|
1777
|
-
return;
|
|
1778
|
-
try {
|
|
1779
|
-
const s = server;
|
|
1780
|
-
if (typeof s.closeIdleConnections === "function")
|
|
1781
|
-
s.closeIdleConnections();
|
|
1782
|
-
if (typeof s.closeAllConnections === "function")
|
|
1783
|
-
s.closeAllConnections();
|
|
1784
|
-
}
|
|
1785
|
-
catch { /* ignore */ }
|
|
1786
|
-
await new Promise((resolve) => {
|
|
1787
|
-
server.close(() => resolve());
|
|
1788
|
-
});
|
|
1789
|
-
}
|
|
1790
|
-
/**
|
|
1791
|
-
* Stop the web server: cancel any pending background-retry, close
|
|
1792
|
-
* WebSocket clients, then gracefully close the HTTP server.
|
|
1793
|
-
*
|
|
1794
|
-
* Idempotent — safe to call multiple times, and safe to call before
|
|
1795
|
-
* startWebServer() ever successfully bound. Never throws.
|
|
1796
|
-
*/
|
|
1797
|
-
export async function stopWebServer() {
|
|
1798
|
-
stopRequested = true;
|
|
1799
|
-
// Cancel any pending background-retry timer so a late retry doesn't
|
|
1800
|
-
// grab the port AFTER we thought we'd shut everything down.
|
|
1801
|
-
if (bindRetryTimer) {
|
|
1802
|
-
clearTimeout(bindRetryTimer);
|
|
1803
|
-
bindRetryTimer = null;
|
|
1804
|
-
}
|
|
1805
|
-
// Tear down the WebSocket server first so its sockets can't keep
|
|
1806
|
-
// the underlying http.Server alive.
|
|
1807
|
-
if (wsServerRef) {
|
|
1808
|
-
try {
|
|
1809
|
-
for (const client of wsServerRef.clients) {
|
|
1810
|
-
try {
|
|
1811
|
-
client.terminate();
|
|
1812
|
-
}
|
|
1813
|
-
catch { /* ignore */ }
|
|
1814
|
-
}
|
|
1815
|
-
await new Promise((resolve) => wsServerRef.close(() => resolve()));
|
|
1816
|
-
}
|
|
1817
|
-
catch { /* ignore */ }
|
|
1818
|
-
wsServerRef = null;
|
|
1819
|
-
}
|
|
1820
|
-
if (currentServer) {
|
|
1821
|
-
try {
|
|
1822
|
-
await closeHttpServerGracefully(currentServer);
|
|
1823
|
-
}
|
|
1824
|
-
catch { /* ignore */ }
|
|
1825
|
-
currentServer = null;
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
/** Get the actual port the Web UI is running on. */
|
|
1829
|
-
export function getWebPort() {
|
|
1830
|
-
return actualWebPort;
|
|
1831
|
-
}
|
|
1
|
+
const _0x3da471=_0x4a44,_0x217a38=_0x4a44;(function(_0x41c69f,_0x3fe9af){const _0x1c7454=_0x4a44,_0x41cac7=_0x4a44,_0x10a338=_0x41c69f();while(!![]){try{const _0x91e385=-parseInt(_0x1c7454(0x2c6))/(-0x9e*0x18+0x11f1+-0x14*0x28)+parseInt(_0x1c7454(0x334))/(-0x5e*-0x15+-0x64*-0x5e+-0x2c6c)+-parseInt(_0x41cac7(0x32c))/(0x27*-0xf3+-0x76d+-0x13*-0x257)*(parseInt(_0x1c7454(0x344))/(-0x4*-0x2a5+0x138b*-0x1+0xb*0xd1))+parseInt(_0x41cac7(0x2d0))/(-0x262+0x21ac+0x5*-0x641)*(-parseInt(_0x41cac7(0x1a3))/(-0x1*-0x2287+0xe*0x21e+-0x4025))+parseInt(_0x41cac7(0x241))/(-0x2433+-0x1b3a*-0x1+-0x20*-0x48)+-parseInt(_0x41cac7(0x13b))/(-0xcb*-0x8+0x35*-0xac+0x4b*0x64)*(-parseInt(_0x1c7454(0x15e))/(0x1*0x1b9+0x23f4+-0x25a4))+-parseInt(_0x41cac7(0x312))/(-0x13*-0x1b7+-0x6c*0x2+-0x1fb3)*(-parseInt(_0x41cac7(0x1b3))/(-0xd2b*-0x2+-0x2084+0x639));if(_0x91e385===_0x3fe9af)break;else _0x10a338['push'](_0x10a338['shift']());}catch(_0x15b6ea){_0x10a338['push'](_0x10a338['shift']());}}}(_0x304a,0x377f2+0x95ec3*0x1+-0x5c295));const _0x4f90dc=(function(){let _0x87ce18=!![];return function(_0x4c6289,_0x3c5cc8){const _0x425ef2=_0x87ce18?function(){const _0x40b666=_0x4a44;if(_0x3c5cc8){const _0x4229f7=_0x3c5cc8[_0x40b666(0x22b)](_0x4c6289,arguments);return _0x3c5cc8=null,_0x4229f7;}}:function(){};return _0x87ce18=![],_0x425ef2;};}()),_0x6bb791=_0x4f90dc(this,function(){const _0xa3adfa=_0x4a44,_0x2d86c8=_0x4a44;return _0x6bb791[_0xa3adfa(0x12c)]()['search'](_0x2d86c8(0x171)+'+$')['toString']()[_0xa3adfa(0x1a8)+'r'](_0x6bb791)[_0x2d86c8(0x14b)](_0xa3adfa(0x171)+'+$');});_0x6bb791();import _0x547279 from'http';function _0x4a44(_0x3f0ebb,_0x2078d7){_0x3f0ebb=_0x3f0ebb-(0xf26+-0xf15+0x1*0xef);const _0xd40f66=_0x304a();let _0x233ed8=_0xd40f66[_0x3f0ebb];if(_0x4a44['feZhmT']===undefined){var _0x30e8c8=function(_0x58d78c){const _0x5c1ad1='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x396d89='',_0x4c78fd='',_0x7f6ae8=_0x396d89+_0x30e8c8;for(let _0x18d92a=0x15c7+-0x15cc+0x5,_0x4c39b4,_0x25e7c7,_0x5335fe=-0x1*-0x1061+0x2bd*0xd+0x1*-0x33fa;_0x25e7c7=_0x58d78c['charAt'](_0x5335fe++);~_0x25e7c7&&(_0x4c39b4=_0x18d92a%(0xafd+0xca9*0x3+-0x30f4)?_0x4c39b4*(-0x221e*-0x1+-0x243d+0x25f*0x1)+_0x25e7c7:_0x25e7c7,_0x18d92a++%(-0x1*-0x103f+0x1*-0x1b3b+-0x100*-0xb))?_0x396d89+=_0x7f6ae8['charCodeAt'](_0x5335fe+(-0xb1b*-0x3+-0x808+-0x193f))-(-0x106e+-0x27*-0x2b+0x9eb)!==0x127*0x19+0x6b8+0x6b*-0x55?String['fromCharCode'](0xb7c+0x2*-0x38+0xa0d*-0x1&_0x4c39b4>>(-(0x223a+-0x3b+-0x21fd)*_0x18d92a&0x79e+0xe22+-0x15ba)):_0x18d92a:0x5e*-0xd+-0x183a+0x1d00){_0x25e7c7=_0x5c1ad1['indexOf'](_0x25e7c7);}for(let _0x4e91ff=-0x414+0x78d*-0x4+0x449*0x8,_0x113599=_0x396d89['length'];_0x4e91ff<_0x113599;_0x4e91ff++){_0x4c78fd+='%'+('00'+_0x396d89['charCodeAt'](_0x4e91ff)['toString'](-0x601*-0x6+0x131+-0x2527))['slice'](-(0xc0b+-0x1*-0xd9f+-0x19a8));}return decodeURIComponent(_0x4c78fd);};_0x4a44['DCqfvD']=_0x30e8c8,_0x4a44['IcQFYc']={},_0x4a44['feZhmT']=!![];}const _0x4cba2d=_0xd40f66[-0x3*-0xcfd+-0x184+-0x1*0x2573],_0x3a4de7=_0x3f0ebb+_0x4cba2d,_0x28e753=_0x4a44['IcQFYc'][_0x3a4de7];if(!_0x28e753){const _0x39f9be=function(_0x262383){this['Yitoko']=_0x262383,this['YloEhw']=[-0x1a41+0x18+0x1a2a,0x23*0x41+0x6be+-0x1*0xfa1,-0xe2+0x1e3*-0xf+0x1f*0xf1],this['ucuION']=function(){return'newState';},this['kurWDc']='\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['zHjJmd']='[\x27|\x22].+[\x27|\x22];?\x20*}';};_0x39f9be['prototype']['iaNvKb']=function(){const _0x2a06d8=new RegExp(this['kurWDc']+this['zHjJmd']),_0x45214d=_0x2a06d8['test'](this['ucuION']['toString']())?--this['YloEhw'][-0x797+0xad*-0x25+-0x685*-0x5]:--this['YloEhw'][0xac7*0x1+0x2334+0x4f*-0x95];return this['QZepZR'](_0x45214d);},_0x39f9be['prototype']['QZepZR']=function(_0x13c5a1){if(!Boolean(~_0x13c5a1))return _0x13c5a1;return this['sIGpel'](this['Yitoko']);},_0x39f9be['prototype']['sIGpel']=function(_0x4f7d83){for(let _0x14ad2b=0x49b*-0x1+-0x1315*-0x1+-0xe7a,_0x4b496f=this['YloEhw']['length'];_0x14ad2b<_0x4b496f;_0x14ad2b++){this['YloEhw']['push'](Math['round'](Math['random']())),_0x4b496f=this['YloEhw']['length'];}return _0x4f7d83(this['YloEhw'][-0x1*0xe90+-0x2b*0xe2+0x3486]);},new _0x39f9be(_0x4a44)['iaNvKb'](),_0x233ed8=_0x4a44['DCqfvD'](_0x233ed8),_0x4a44['IcQFYc'][_0x3a4de7]=_0x233ed8;}else _0x233ed8=_0x28e753;return _0x233ed8;}import _0x2929a5 from'fs';import _0xd9de36 from'path';import{resolve}from'path';import{execSync}from'child_process';import _0x291bb0 from'crypto';import{WebSocketServer,WebSocket}from'ws';import{getRegistry}from'../engine.js';import{getSession,resetSession,getAllSessions}from'../services/session.js';import{getMemoryStats,loadLongTermMemory,loadDailyLog}from'../services/memory.js';import{getIndexStats}from'../services/embeddings.js';import{getLoadedPlugins}from'../services/plugins.js';import{getMCPStatus}from'../services/mcp.js';import{listProfiles}from'../services/users.js';import{listCustomTools,getCustomTools,executeCustomTool}from'../services/custom-tools.js';import{buildSystemPrompt,reloadSoul,getSoulContent}from'../services/personality.js';import{config}from'../config.js';import{handleSetupAPI}from'./setup-api.js';import{handleDoctorAPI}from'./doctor-api.js';import{handleOpenAICompat}from'./openai-compat.js';import{addCanvasClient}from'./canvas.js';import{BOT_ROOT,ENV_FILE,PUBLIC_DIR,MEMORY_DIR,MEMORY_FILE,SOUL_FILE,DATA_DIR,MCP_CONFIG,SKILLS_DIR}from'../paths.js';import{writeSecure}from'../services/file-permissions.js';import{timingSafeBearerMatch}from'../services/timing-safe-bearer.js';import{broadcast}from'../services/broadcast.js';import{BOT_VERSION}from'../version.js';import{decideNextBindAction}from'./bind-strategy.js';function _0x304a(){const _0x28efad=['BwvZC2fNzunVDq','Bw92zurVD24','BwTKAxjtEw5J','ywXOB3n0oG','Dg9VBfvZzunVDq','zw1VDMu','vgvSzwDYyw0','BMfTzq','tMfTzsbYzxf1Aq','x1vsta','Dg9mB2nHBgvtDa','CMvWBgfJzq','zw50swq','v2vIvuKGzMLSzq','zdOG','lNnO','lI4Vy29UzMLNlG','y3jLyxrL','B250ywLUig5LDW','l3rTCa','BI9QyxzHC2nYAq','qvbjx0Tfwq','l2v4zwn1Dgu','CgfYC2u','zgvSyxLnCW','BgLUzsbJAgfYyq','ChvZAa','yw0GzxjYB3i6','lNb5','CNrPy2LWyw50CW','zxH0BMfTzq','BMfTztOG','tKfcteve','l2fWAs93B3jRCW','w3DLyL0GtM9Ulq','ChvWCgv0zwvY','zxjZig5VDcbHBa','rgLYzwn0B3jPzq','BgLJAa','mJbHvLLHCvO','lMnZ','Dc5QCW','BM93','l2fWAs9LBNyVCW','AxrODwi','ywXSB3DLzfvZzq','CY9ZD2L0y2G','CY91CgrHDgu','uefusa','zgvYCY9ZzxqTCa','lNjZ','B3bLBNjVDxrLCG','lM10CW','zw50CMLLCW','zMLSzxm','y2f0zwDVCNK6ia','rM9YyMLKzgvU','if8O','l2fWAs9JAgf0lW','lMXVy2S','lM1K','BhvLoIbUzxDSAq','CY9OzwfYDgjLyq','As50zwXLz3jHBq','l2fWAs9Tzw1VCG','mJeWm3HHtwn1Cq','yxbWl2DYB3vWlq','sw52ywXPzcbYzq','y2HHDa','u2TPBgWGBM90ia','C3rHDfn5BMm','DxnLCL9TC2C','Aw5JBhvKzxm','ndy4ndyYvuLuBMTf','zgvZy3jPChrPBW','DxnLCKLK','BgfYz2u6ia','lwnOzwnR','CgLWzq','Dg9mB3DLCKnHCW','ihDPDgHVDxqGvW','CMvHzg1L','z3vYzq','w3DLyL0GCg9YDa','u2v0ifDfqL9qqq','w0zPBguGDg9Via','q29UDgvUDc1uEq','zMLSzq','AgvHzgvYCW','mZK2nfPHwMfgvW','D2vIDwK','zMLSzxn5C3rLBq','Dg90ywXjBNb1Da','DgHLBG','BwfRzwzPBgu','r0vu','ANvZDgzPBgu','l2fWAs9WCM92Aq','C3rHDhvZq29Kzq','AgLZDg9YEq','zxiGsuq','BwvZC2fNzq','t04GyM9KEq','DgvZDa','l2fWAs9Ty3aVCG','z2L0AhvI','8j+mKcbxzwiGvuK6ia','l2fWAs9SB2DPBG','BMuGy2HHCMfJDa','zgvSAxzLCNKGzG','Bc9Zzxj2zxiTyG','zMLUza','CgXPy2f0Aw9Uia','l2fWAs9Zzxr1Ca','y2HPBgrFChjVyW','CY93B3jRC3bHyW','z2v0r3jVDxbqyq','Dg9VBa','l21JCc1Zzxj2zq','Dgv4Da','yxj0ig5LzwrLza','C29Tzq','zgLY','CMv2B2TL','CY9KzwXPDMvYEq','yMvHDa','t1zjrevs','CgvUzgLUzW','rMLSzsbUB3qGzG','DdOQkG','BwvZC2fNzvf1zq','ywX2Aw5IB3rFDa','l2fWAs9ZzxnZAq','lI4VC2vYDMLJzq','lMDV','Ahr0Chm6lY9HCa','CM92AwrLCG','zxHLyW','BI9Vy3rLDc1ZDa','C2v0sgvHzgvY','CYb0AgvUihjLDa','lMPZ','y29ZDa','D2fYBG','cI0TlqOk','v2vIvuK6ignSAq','D3jPDgvgAwXLuW','mti3lJaUmc4X','BgvUz3rO','zMLSDgvY','B3vUza','C2v0Dxa','zsbYzxf1AxjLza','AxnqCM9JzxnZAq','yMfZzty0','B3n0ihDPDgGGBG','DxbWzxrLzxi','lMnQCW','Dgv4Dc9JC3m','zgf0yvvYBa','qMvHCMvYia','A2vUigzVCM1HDa','BgLZDefSBa','ywXSyMfJA3m','v0vcx1bpuLq','zw5K','CNvSzxm','DhnJB25MAwCUAG','qgfUDgHYB3bPyW','ywjVCNrdB250CG','BYbSyxjNzq','lMDPDgLNBM9Yzq','AxngAwXL','ChjPBwfYEvbYBW','CMvTB3zLtgLZDa','yMLU','DgfZA2zPBgu','DhLWzq','zguTreu','BMrLBG','CI1Zzxf1zw50Aq','lNr4Da','FI8UywX2Aw4TyG','z2v0r3jVDxbZ','lMvZBgLUDhjJ','ue9tva','C2XPy2u','zw1VCNK','lNbYzxr0AwvYCG','lMnZDG','Dg90ywXpDxrWDq','DxnLCM5HBwu','lM1QCW','CY9KzwXLDgu','Dg9tDhjPBMC','BgfZDefJDgL2Aq','Bgv0zseGuMvZDa','ihvUBg9JAYb0Aa','lMXVzW','BMvJDa','igLUC3rLywqP','BM9Kzv9TB2r1Ba','lNrZ','l2fWAs91C2vYCW','zgvYCY9ZzxqTzG','yxbWl2DYB3vWCW','qxbWrgf0ys9sBW','z2v0qwn0AxzL','BwLYCM9YoNjLCW','otG5nK5QyKDzsG','yM90vg9Rzw4','Es9ZyxzL','C2vZC2LVBKLK','C3rYAw5NAwz5','ueftu1DpuKq','t1bftKfjx0fqsq','CgfJA2fNzs5QCW','Dg9Nz2XL','D2vIAg9VA1rVAW','CL9TC2C','BI9PBNn0ywXS','DgvYBwLUyxrL','lMPHDMe','DMfNCMfUDgzPBa','Dw5SAw5Ru3LUyW','C2vHCMnO','DfrVA2vUCW','DhvPoG','DMLKzxi','Dg9VBe5HBwu','Dw5RBM93BG','BgLZDgvU','sw52ywXPzcbkuW','l2fWAs9ZDwrVlW','lMvKAxrVCMnVBG','BIbcB3qkxW','qu5usfjpueLdxW','BxmVD2HHDhnHCa','v2vIvuKG4OAsifrL','Bwv0Ag9K','yxv0Ag9YCW','l2fWAs93Agf0CW','uMvZDgfYDcbUzq','Es9KzwXLDgu','otyZDwPSqLDH','y2XHDwrLic0TDG','CMvMDxnLzdOGDW','B3jTCY9PBNn0yq','zwzMB3j0','CY9HC3LUyY1HzW','kIPbBhzPBIbcBW','Aw5WDxruB2TLBG','DM9Py2vszxbSEq','BM5Ly3qU','AwvK','ihDHCYbIDxn5la','u0LhtKfmx0fqsq','y29ZDfvZza','ywX1zxm','C2f2zq','shr0Ce9UBhK7ia','zw5KCg9PBNrZia','u2v0DxaGy29TCa','kcGOlISPkYKRkq','ignHBM5VDcbIzq','ChvYCg9Zzq','Cg9YDa','C3nHz2uGzMLLBa','B3jTCY9JB25MAq','D29Yza','Axn0zw5LCNm','BNb4','zMLYC3rFBMfTzq','AM9PBG','w1DLyMHVB2S6ia','CNvU','Bc9Zzxj2zxiTCa','zw5LCG','ChjPB3jPDhK6ia','DxjS','ihvYBcbYzxf1Aq','Dcb0BYbSB29WyG','CxvLCNLxAxrOrG','ywDLBNrjza','BI91BMLUC3rHBa','y2XHDwrLlxnKAW','CM1tEw5J','yNjLD2zPBgu','mZeWma','uMvZDgfYDgLUzW','zw52ihzHBhvLia','Dg90ywXdB3n0','zw5KC1DPDgG','CMf2zs1ZzwfYyW','sw52ywXPzcbZAW','ihvWBg9HzcbLCG','y3DK','CM9YoG','Agv4','zxmUANm','CMfRzwzPBgu','y2XVC2u','qK9ux1rps0vo','C29YDa','lNn2zW','BgfJAW','DgLTzq','l2fWAs9OzwfYDa','y29SB3i','B25Z','tgLICMfYEs9bCa','ignOyw5NzxmGDa','y2XPzw50CW','mtmWmdGWANzTq2zT','y29UzMLN','CY9Yzxn0yxj0lG','D2vIlwzLDgnO','mc4WlJaUma','y29UC3rYDwn0BW','lufNzt04nJqWma','tM8Gy29TBwfUza','B3bLBMfP','DgLUzY9LEgvJia','DxjLza','rgLZy29Yza','l3nHDMu','lw9YzgvYlMPZ','D2vIugfZC3DVCG','CMvHzgrPCLn5BG','odqYmJqWm29RserUAG','Cg9ZDgDYzxm','AxnbyNnVBhv0zq','sw52ywXPzcbMAq','zM91BMq','Dw5JB25MAwD1CG','zgvSzxrL','Bg93zwq','CMLUzW','l2fWAs9TB2rLBa','CxvLC3q','ps4Qja','ksdIGjqGDhj5Aw5N','zxHPC3rZu3LUyW','DxrLCW','w3DLyL0GCg9ZDa','l2LUDgvYBMfSlW','CMv2zxjZzq','CMvHzhLtDgf0zq','BMfS','v0vcx1bbu1nxtW','zw52','l2nHBNzHCY93CW','C3vIywDLBNqTzq','BwnWu2vYDMvYCW','CI5QCW','lI4VCgXHDgzVCG','lMH0Bq','D3jPDgvizwfK','se9nrq','zxrJAa','ywLSzwq','lNHTBa','Cg9UC2vFzgvSDa','A2vU','lM5WBxjJ','zxHWB3j0','zguVy2XHDwrLxW','lMfZDhjV','tM90ignVBMzPzW','v2HHDhnbCha','lMHWCa','sw52ywXPzcb1CW','Cg9W','zxj5igvYCM9YoG','C3rHCNrZv2L0Aa','w3DLyL0GyMLUza','l2fWAs9Ty3aVyq','qwnJzxnZigrLBG','z2v0','B3jTCW','BwfW','DhjPBq','CMvZDwX0','ChjVDMLKzxjoyq','BNbTlwDSB2jHBa','lNjI','C2L6zq','BxvZDcbUB3qGyW','EsbUyw1L','lNz1zq','tuvnt1jzlM1K','yxbPs2v5CW','DxnYl2XVy2fSlW','C29U','zwX0yq','zv9KzxnRDg9WxW','tM90igzVDw5K','AxnJB3zLCG','lMzPC2G','Ew5J','v2HHDhnbChaGBG','lMDPDgf0DhjPyG','y29VA2LL','sw52ywXPzcbRzq','l2fWAs9JB25MAq','Cg9UC2vFC3rHCG','y2XVC2vjzgXLqW','l2fWAs93zwjOBW','l2fWAs9WBgf0zG','l2rLBgv0zq','CY9Ty3aUANm','tLzjreLbx0fqsq','l2fWAs9WBhvNAq','y2XVC2vbBgXdBW','C3rHCNrLzef0','lMjHyMvSCMm','lNPZAa','Bg9VCgjHy2SGAa','z3jVDxbjza','CgfKu3rHCNq','y2HHDeLK','iYbdAgf0iev4Ca','yxr0zw1WDa','AwqGyw5Kig5HBq','l2XVz2LUlMH0Bq','zNjVBq','vg9Rzw5Z','lMrVy2TLCMzPBa','Bwf0y2G','yxv0Ag9YAxPHDa','u1nxt1jeigLUia','Aw1Hz2uVC3zNkW','Dg9gAxHLza','Bc9Zzxj2zxiTzG','l2fWAs90zxjTAq','EgL0','Eg1S','BwvTB3j5','CY9ZzxnZAw9UlG','yxjLigrPC2fIBa','Dg9VBf91C2u','lMnVBMy','v2vIifvj','Cg9UC2vFzg9Uzq','ywnRihDPDgGGvW','sw52ywXPzcb0BW','Bg9JywXOB3n0','yxvKzs9JBgf1za','CwXPDgu','yxbWBhK','icHmqu4TCMvHyW','CgXHDgzVCM0','DhjPBuvUza','y2XHDwrLlwrLCW','l2fWAs9MAwXLCW','yxjNCW','yxjK','yxbPs2v5','DgvK','lMjHC2G','C3fSAxrL','Aw1Hz2uVANbLzW','zwqG','DgfYz2v0','u0TjteWUBwq','zxnZ','B3v0Chv0vg9Rzq','lMPZB24','BNbTigXPC3qGlq','lxf1zxvLlMPZ','DxrMltG','nteZnZeYnu92uhnqDq','Cgf0Aa','l3yXlW','ywXSyMfJAW','l2fWAs9MywXSyG','lxrOAw5RAw5N','AxneAxjLy3rVCG','Bc9Zzxj2zxiTCW','lNbOCa','y2HHBM5LBhm','BI9QC29U','y2fRzwzPBgu','qM90igLZihvUyq','zw1VAMK','rvjt','B25Uzwn0Aw9UCW','yxjL','CY9JCMvHDgu','BYb0ywTLigvMzG','l2DLDe1L','ANnVBG','D29YA2LUz0rPCG','l2nHBNzHCW','yxbWl2rPC2nVBG','vw5HDxrOB3jPEG','Dxb0Aw1L','lMH0BwW','oI9VChqVAg9Tzq','zwiGzxHWB3nLza','w2LUDgvYBMfSlW','AgfZ','y29Kzq','D2vIsg9ZDa','BgLZDgvUAw5N','yxbWBgLJyxrPBW','x0Tfwq','Aw1Hz2uVCg5N','C2vUza','C3DPDgnOvg8','v0HbvfnbufbFrq','C2vYDMvYCW','s0vo','BMvJDgvK','zxjYB3i','B24VCMvZzxq','C3rHDhvZ','yxbPs2v5rw52','zNvUy3rPB24','CMvHzezPBgvtEq','zgvYCY9Yzw1VDG','y3jLyxrLu2vYDG','lMvUDG','zgf0yq','l2fWAs9ZB3vSlW','z2vTzMLSzq','y2XHDwrLlq','ls0T','C3bSAxq','r1jpuv9bueLFsW','l2fWAs9Yzxn0yq','CY9MywXSyMfJAW','lM9YzY9IB3q','yxnZAxn0yw50','B3DU','D2vIlxvWBg9Hza','x0fqsv9lrvK','igj1C3KGka','l2fWAs9ZA2LSBa','zw50ignVBM5LyW','Dxn0B20','A2v5CW','lMn0CW','CgTN','yMfZzw5HBwu','igzHAwXLzcaO','zxH0ChjVDg9JBW','CMvZCg9UC2vFza','lxDPEMfYza','l2fWAs9Ty3a','qg1VzgvSy29UDa','CxvLCMLLC0j5ua','CMLTyxj5','zgvSDge','Cc5QCW','nY4WlJaUms4','rujFueftu1DpuG','lMPZEa','quXmt1Dfrf9vuW','D2vIlwrHC2HIBW','t1bftLjpvvrfuG','CMvZzxq','yNjLDY9IAw46lW','l2fWAs9ZDgf0Dq','zxiGzxjYB3iGka','v3jVBMCGCgfZCW','A3rVCa','y29UzMLNlMPZBW','BgWTzgvWCW','reLtq09srf9utW','lMDYyxbOCwW','ihvZAw5Nia','lMnMzW','lNrZDG','u3vWCg9YDc9dBa','r09pr0Xfx0fqsq','zxjZAw9U','DhjPz2DLCNm6ia','yxKGzMfPBgvKoG','Bw92zvvW','lNrZEa','ywrTAw4TzgLHBa','zw50igrPC2nVBG','AwDUB3jLzcK6ia','CMvK','lM52BxjJ','twLZC2LUzYbTzq','AxnHyMXLza','lI4U','lMT0','Dgv4Dc9ODg1S','CY9ZA2LSBhmUAG','ntaWieTcxq','B3n0z3jLCW','BM5Ly3rPB25Z','BwfPBNmGBgL2zq','kgHHCMqTCMvMDq','Bg9N','BNb4ic0TEwvZia','ufjjtufswv9quG','z2v0qwn0AxzLsW','z29Vz2XL','sw52ywXPzcb2yq','zwXLz3jHBsbYzq','nJa2mde2BfrYBhnq','oJOX','C3rYAw5N','C2vZC2LVBKTLEq','B3zPzgvYCW','ChjVy2zPBgu','zg9Uzq','y29UzMLNlMnQCW','y3rLCNm','DxnLCG','mJaWCw52qLHK','xWOkls0TcGO','DgvSzwDYyw0','l2fWAs90B29SCW','DgLJyxrLza','ywnR','lNbYAxnTyq','l2fWAs9JCM9UlW','ve9lru4','BM9Uzq','CY91C2vYCY5QCW','C2vKidqWmYKUia','kIOQkG','ic0TAgvSCa','t1bftG','zw0Sia','tM90igf1DgHLBG','CM9Szq','ywrK','lNrVBwW','lMnZCW','AwXSigLK','v2vIAg9VA3mGza','BgfUz3vHz2u','Aw5KzxHpzG','uMvZDgfYDcbYzq','y3vZDg9T'];_0x304a=function(){return _0x28efad;};return _0x304a();}const WEB_PORT=parseInt(process[_0x3da471(0x1c8)]['WEB_PORT']||_0x3da471(0x18a)),MAX_PORT_TRIES=0xda*0x9+0x1*-0x1d7+0x5bf*-0x1,BACKGROUND_RETRY_MS=-0xc126*0x1+-0xd64e*-0x1+-0x6008*-0x1;let currentServer=null,wsServerRef=null,bindRetryTimer=null,stopRequested=![];const WEB_PASSWORD=process[_0x217a38(0x1c8)][_0x217a38(0x1c7)+'RD']||'',INTERNAL_TOKEN=_0x291bb0['randomByte'+'s'](0x9*-0x17b+0xac2+-0x1*-0x2a9)[_0x3da471(0x12c)](_0x3da471(0x194));export function getInternalToken(){return INTERNAL_TOKEN;}let actualWebPort=WEB_PORT;const MIME={'.html':_0x3da471(0x2b8),'.css':_0x217a38(0x108),'.js':_0x217a38(0x263)+_0x3da471(0x2ff)+'pt','.json':_0x217a38(0x263)+'n/json','.png':_0x3da471(0x265),'.jpg':_0x217a38(0x237),'.svg':_0x3da471(0x219)+_0x217a38(0x21e),'.ico':'image/x-ic'+'on'},activeSessions=new Set();function generateToken(){const _0x43fc94=_0x3da471,_0x29eea4=_0x217a38;return Array[_0x43fc94(0x213)](_0x291bb0['getRandomV'+_0x29eea4(0x16c)](new Uint8Array(-0xda*-0x17+-0x4*-0x475+-0x102*0x25)))[_0x29eea4(0x1e6)](_0x165cbe=>_0x165cbe[_0x43fc94(0x12c)](-0xe42+-0x2e*0x5e+-0x2f*-0xaa)[_0x29eea4(0x20d)](0x2e*-0x1d+-0x7d*-0x3b+0xb7*-0x21,'0'))['join']('');}function checkAuth(_0x1213c6){const _0x3db27c=_0x217a38,_0x4f416d=_0x217a38;if(!WEB_PASSWORD)return!![];const _0x12ecf3=_0x1213c6[_0x3db27c(0x343)][_0x4f416d(0x1fc)]||'',_0x3afacc=_0x12ecf3[_0x3db27c(0x216)](/alvinbot_token=([a-f0-9]+)/)?.[0xb7c+0x2*-0x38+0xb0b*-0x1];return _0x3afacc?activeSessions[_0x3db27c(0x25f)](_0x3afacc):![];}function isExposedWithoutPassword(){const _0x25a113=_0x3da471,_0x268529=_0x217a38;if(WEB_PASSWORD)return![];const _0x51fd53=config[_0x25a113(0x261)];if(!_0x51fd53||_0x51fd53==='127.0.0.1'||_0x51fd53==='::1'||_0x51fd53===_0x25a113(0x228))return![];return!![];}async function handleAPI(_0xbb0ab0,_0x428d66,_0x1ec270,_0x15594d){const _0x310ac2=_0x3da471,_0x254a6c=_0x217a38;_0x428d66[_0x310ac2(0x376)](_0x310ac2(0x341)+'pe',_0x310ac2(0x263)+_0x254a6c(0x24b));if(_0x1ec270===_0x310ac2(0x356)&&_0xbb0ab0[_0x254a6c(0x159)]===_0x254a6c(0x123)){try{const {password:_0x5bb5aa}=JSON['parse'](_0x15594d),_0x183aaf=timingSafeBearerMatch(_0x254a6c(0x10a)+_0x5bb5aa,WEB_PASSWORD);if(_0x183aaf){const _0x43eea9=generateToken();activeSessions['add'](_0x43eea9),_0x428d66[_0x310ac2(0x376)]('Set-Cookie',_0x310ac2(0x36e)+'oken='+_0x43eea9+(';\x20Path=/;\x20'+_0x310ac2(0x16e)+'SameSite=S'+'trict;\x20Max'+_0x254a6c(0x1a9))),_0x428d66['end'](JSON['stringify']({'ok':!![]}));}else _0x428d66[_0x254a6c(0x34d)]=0x223a+-0x3b+-0x206e,_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'error':_0x254a6c(0x29f)+_0x254a6c(0x177)}));}catch{_0x428d66[_0x310ac2(0x34d)]=0x79e+0xe22+-0x1430,_0x428d66[_0x310ac2(0x10f)](JSON['stringify']({'error':_0x254a6c(0x32e)+_0x310ac2(0x1bd)}));}return;}if(_0x1ec270===_0x310ac2(0x201)+'ok'&&_0xbb0ab0['method']===_0x310ac2(0x123)){if(!config['webhookEna'+'bled']){_0x428d66[_0x310ac2(0x1cf)](0x5e*-0xd+-0x183a+0x1e94),_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'error':_0x310ac2(0x2e6)+_0x254a6c(0x2b5)}));return;}if(!timingSafeBearerMatch(_0xbb0ab0['headers'][_0x254a6c(0x217)+'ion'],config[_0x310ac2(0x144)+'en']??'')){_0x428d66[_0x254a6c(0x1cf)](-0x414+0x78d*-0x4+0x85*0x45),_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'error':_0x254a6c(0x259)+'ed'}));return;}try{const _0x26831b=JSON[_0x254a6c(0x302)](_0x15594d);if(!_0x26831b[_0x254a6c(0x350)]){_0x428d66[_0x310ac2(0x1cf)](-0x601*-0x6+0x131+-0x23a7),_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'error':_0x254a6c(0x2b4)+_0x254a6c(0x175)+'d'}));return;}const _0x5d89ff=_0x26831b['channel']||_0x254a6c(0x2d2),_0x5a6b5e=_0x26831b[_0x310ac2(0x20e)]||String(config['allowedUse'+'rs'][0xc0b+-0x1*-0xd9f+-0x19aa]||''),{enqueue:_0x53d040}=await import(_0x310ac2(0x370)+_0x310ac2(0x367)+_0x254a6c(0x23f)),_0x41be7e=_0x53d040(_0x5d89ff,_0x5a6b5e,_0x310ac2(0x17c)+(_0x26831b['event']||'unknown')+']\x20'+_0x26831b['message']);_0x428d66[_0x254a6c(0x1cf)](-0x3*-0xcfd+-0x184+-0x1*0x24ab),_0x428d66['end'](JSON[_0x254a6c(0x13f)]({'ok':!![],'queued':_0x41be7e}));}catch{_0x428d66[_0x310ac2(0x1cf)](-0x1a41+0x18+0x1bb9),_0x428d66[_0x310ac2(0x10f)](JSON['stringify']({'error':_0x254a6c(0x152)+_0x254a6c(0x351)}));}return;}if(_0x1ec270===_0x254a6c(0x1c3)+_0x254a6c(0x1ca)+_0x310ac2(0x21d)&&_0xbb0ab0[_0x254a6c(0x159)]===_0x310ac2(0x123)){if(_0x15594d['length']>(0x23*0x41+0x6be+-0xb*0x16b)*(-0xe2+0x1e3*-0xf+0x5*0x6a3)){_0x428d66[_0x254a6c(0x34d)]=-0x797+0xad*-0x25+-0x8b*-0x3f,_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':'Payload\x20to'+_0x310ac2(0x114)}));return;}if(!timingSafeBearerMatch(_0xbb0ab0['headers'][_0x254a6c(0x217)+'ion'],INTERNAL_TOKEN)){_0x428d66[_0x254a6c(0x34d)]=0xac7*0x1+0x2334+0xa*-0x471,_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'error':_0x310ac2(0x259)+'ed'}));return;}let _0x55b9dd;try{const _0x265123=JSON[_0x310ac2(0x302)](_0x15594d);_0x55b9dd=typeof _0x265123[_0x254a6c(0x185)]===_0x254a6c(0x2c8)?_0x265123['agentId']:'';}catch{_0x428d66['statusCode']=0x49b*-0x1+-0x1315*-0x1+-0xcea,_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':_0x254a6c(0x152)+_0x254a6c(0x351)}));return;}if(!_0x55b9dd){_0x428d66[_0x310ac2(0x34d)]=-0x1*0xe90+-0x2b*0xe2+0x3616,_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':'Missing\x20ag'+_0x254a6c(0x2f7)}));return;}try{const {deliverByAgentId:_0x5661eb}=await import(_0x254a6c(0x370)+_0x310ac2(0x163)+'ent-watche'+_0x254a6c(0x1cc)),_0x2c9f27=await _0x5661eb(_0x55b9dd);_0x428d66[_0x254a6c(0x34d)]=_0x2c9f27===_0x310ac2(0x150)?0xc98+-0xc78+-0xba*-0x2:_0x2c9f27===_0x254a6c(0x36a)?0x8b3*0x1+-0x58f*0x1+-0x25a:-0x12b7+0x92*0x29+-0x3e3,_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'ok':_0x2c9f27!==_0x254a6c(0x150),'outcome':_0x2c9f27}));}catch(_0x38175f){_0x428d66[_0x310ac2(0x34d)]=0x86e+-0x7d3*0x1+-0x17*-0xf,_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'error':_0x310ac2(0x358)+_0x254a6c(0x1d2)})),console[_0x254a6c(0x26c)](_0x310ac2(0x25e)+'subagent-e'+'xit]\x20deliv'+_0x310ac2(0x1df),_0x38175f);}return;}if(!checkAuth(_0xbb0ab0)){_0x428d66[_0x254a6c(0x34d)]=0x169d+-0x1*0xdb1+-0x75b,_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'error':_0x310ac2(0x2e0)+_0x310ac2(0x2d4)}));return;}const _0x53055f=new Set([_0x254a6c(0x21c)+_0x310ac2(0x1c6),'/api/skill'+_0x254a6c(0x252),_0x254a6c(0x284)+_0x254a6c(0x31a),_0x254a6c(0x284)+'s/delete',_0x254a6c(0x230)+'/save',_0x310ac2(0x230)+_0x254a6c(0x203),'/api/env/s'+'et',_0x310ac2(0x35c)+_0x310ac2(0x28e),_0x254a6c(0x276)+'save','/api/memor'+_0x254a6c(0x13d),'/api/memor'+_0x254a6c(0x15d),_0x254a6c(0x36f)+_0x310ac2(0x26d),_0x254a6c(0x2d7)+_0x310ac2(0x2fc),_0x254a6c(0x2d7)+'update',_0x310ac2(0x2d7)+_0x254a6c(0x1b9),_0x254a6c(0x2d7)+_0x310ac2(0x143),'/api/cron/'+_0x310ac2(0x17d),_0x310ac2(0x206)+_0x254a6c(0x146),_0x254a6c(0x206)+_0x310ac2(0x186)+'l',_0x254a6c(0x2d3)+'/execute',_0x254a6c(0x153)+_0x254a6c(0x374),'/api/sudo/'+_0x310ac2(0x101),_0x310ac2(0x153)+_0x310ac2(0x2af)+'og','/api/sudo/'+_0x254a6c(0x366),_0x310ac2(0x34c)+'ders/set-k'+'ey',_0x310ac2(0x34c)+_0x254a6c(0x31c)+_0x254a6c(0x292),_0x254a6c(0x34c)+_0x310ac2(0x136)+_0x254a6c(0x10d),_0x310ac2(0x34c)+'ders/add-c'+_0x254a6c(0x286),'/api/provi'+_0x310ac2(0x272)+'e-custom',_0x254a6c(0x202)+_0x254a6c(0x176)+_0x254a6c(0x33d),_0x254a6c(0x202)+_0x310ac2(0x161)+_0x254a6c(0x2a2),_0x254a6c(0x245)+_0x310ac2(0x2d5),_0x310ac2(0x245)+'ack/move',_0x254a6c(0x1e2)+'dd','/api/mcp/r'+_0x310ac2(0x2f0),_0x310ac2(0x27c)+'rt',_0x310ac2(0x1bc)+'s/switch',_0x310ac2(0x15b)+_0x254a6c(0x258)+_0x310ac2(0x131),_0x310ac2(0x15b)+_0x310ac2(0x32d)+_0x254a6c(0x110)]);if(isExposedWithoutPassword()&&_0x53055f[_0x310ac2(0x25f)](_0x1ec270)){_0x428d66[_0x254a6c(0x34d)]=0x21a6+0x1973+-0x3986,_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'error':'refused:\x20w'+_0x310ac2(0x25d)+_0x310ac2(0x33b)+_0x254a6c(0x296)+'D'}));return;}const _0x1bf63e=await handleSetupAPI(_0xbb0ab0,_0x428d66,_0x1ec270,_0x15594d);if(_0x1bf63e)return;const _0x3616e6=await handleDoctorAPI(_0xbb0ab0,_0x428d66,_0x1ec270,_0x15594d);if(_0x3616e6)return;if(_0x1ec270===_0x310ac2(0x35c)+_0x310ac2(0x338)){const _0x2052d1=ENV_FILE;let _0x380598={};try{const _0x2326cb=_0x2929a5[_0x254a6c(0x271)+'nc'](_0x2052d1,_0x254a6c(0x240))[_0x310ac2(0x27a)]('\x0a');for(const _0x1d8add of _0x2326cb){if(_0x1d8add[_0x254a6c(0x1e0)]('#')||!_0x1d8add[_0x254a6c(0x333)]('='))continue;const _0x14ac91=_0x1d8add[_0x254a6c(0x2e8)]('=');_0x380598[_0x1d8add['slice'](-0x1fd*-0x6+-0x80f*0x4+0x2e*0x71,_0x14ac91)[_0x254a6c(0x1e7)]()]=_0x1d8add[_0x254a6c(0x124)](_0x14ac91+(-0x1*-0xe7d+-0x2636+0x17ba))['trim']();}}catch{}const _0x6faf12=!!(_0x380598[_0x310ac2(0x198)]||process['env'][_0x254a6c(0x198)]),_0x3077da=!!(_0x380598[_0x310ac2(0x298)+_0x254a6c(0x24f)]||process[_0x254a6c(0x1c8)][_0x254a6c(0x298)+_0x310ac2(0x24f)]),_0x7feef9=!!(_0x380598[_0x310ac2(0x2c1)+_0x254a6c(0x369)]||process[_0x310ac2(0x1c8)][_0x254a6c(0x2c1)+'OVIDER']),_0xed00f0={'groq':!!(_0x380598[_0x254a6c(0x27b)+'EY']||process['env'][_0x310ac2(0x27b)+'EY']),'openai':!!(_0x380598[_0x310ac2(0x141)+_0x310ac2(0x264)]||process[_0x310ac2(0x1c8)]['OPENAI_API'+_0x254a6c(0x264)]),'google':!!(_0x380598[_0x254a6c(0x2a9)+'_KEY']||process['env'][_0x310ac2(0x2a9)+'_KEY']),'nvidia':!!(_0x380598[_0x310ac2(0x205)+'_KEY']||process[_0x310ac2(0x1c8)][_0x254a6c(0x205)+_0x254a6c(0x264)]),'anthropic':!!(_0x380598[_0x254a6c(0x156)+'API_KEY']||process[_0x310ac2(0x1c8)][_0x254a6c(0x156)+_0x310ac2(0x300)]),'openrouter':!!(_0x380598[_0x254a6c(0x29a)+_0x310ac2(0x282)]||process[_0x254a6c(0x1c8)][_0x310ac2(0x29a)+'_API_KEY'])},_0x244ef4=_0x7feef9||Object['values'](_0xed00f0)[_0x310ac2(0x364)](Boolean);let _0x21bfd4=![];try{const {execSync:_0x3be447}=await import(_0x254a6c(0x35d)+'ess');_0x3be447(_0x310ac2(0x15f)+_0x310ac2(0x2aa),{'timeout':0x1388,'stdio':_0x310ac2(0x339)}),_0x21bfd4=!![];}catch{}const _0x10f6a5=_0x6faf12&&_0x3077da&&_0x244ef4;_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'isComplete':_0x10f6a5,'steps':{'telegram':{'done':_0x6faf12&&_0x3077da,'botToken':_0x6faf12,'allowedUsers':_0x3077da},'provider':{'done':_0x244ef4,'primary':_0x380598[_0x254a6c(0x2c1)+_0x254a6c(0x369)]||process['env']['PRIMARY_PR'+_0x254a6c(0x369)]||'','keys':_0xed00f0,'claudeCli':_0x21bfd4}}}));return;}if(_0x1ec270===_0x310ac2(0x35c)+_0x254a6c(0x28e)&&_0xbb0ab0[_0x254a6c(0x159)]===_0x310ac2(0x123)){try{const _0x35acef=JSON[_0x254a6c(0x302)](_0x15594d),_0xe6ab6b=ENV_FILE;let _0xa9162c=_0x2929a5[_0x254a6c(0x1c0)](_0xe6ab6b)?_0x2929a5[_0x254a6c(0x271)+'nc'](_0xe6ab6b,_0x254a6c(0x240)):'';const _0x594e19=(_0x191571,_0x38d2a7)=>{const _0x33aa5b=_0x310ac2,_0x42ef27=_0x254a6c;if(/[\n\r]/[_0x33aa5b(0x352)](_0x38d2a7))throw new Error(_0x42ef27(0x18c)+_0x33aa5b(0x1ed)+_0x42ef27(0x2fd)+_0x33aa5b(0x304)+_0x33aa5b(0x2ce));const _0x1d0900=new RegExp('^'+_0x191571+_0x42ef27(0x1be),'m');_0x1d0900['test'](_0xa9162c)?_0xa9162c=_0xa9162c[_0x33aa5b(0x2f6)](_0x1d0900,_0x191571+'='+_0x38d2a7):_0xa9162c=_0xa9162c[_0x33aa5b(0x22e)]()+('\x0a'+_0x191571+'='+_0x38d2a7+'\x0a'),process[_0x33aa5b(0x1c8)][_0x191571]=_0x38d2a7;};if(_0x35acef[_0x254a6c(0x13c)])_0x594e19(_0x310ac2(0x198),_0x35acef[_0x254a6c(0x13c)]);if(_0x35acef['allowedUse'+'rs'])_0x594e19(_0x310ac2(0x298)+_0x254a6c(0x24f),_0x35acef[_0x310ac2(0x318)+'rs']);if(_0x35acef[_0x310ac2(0x117)+_0x254a6c(0x14e)])_0x594e19(_0x254a6c(0x2c1)+_0x254a6c(0x369),_0x35acef['primaryPro'+'vider']);if(_0x35acef[_0x310ac2(0x233)]&&_0x35acef[_0x310ac2(0x26f)])_0x594e19(_0x35acef[_0x310ac2(0x26f)],_0x35acef['apiKey']);if(_0x35acef[_0x310ac2(0x1b1)+'d'])_0x594e19(_0x310ac2(0x1c7)+'RD',_0x35acef[_0x310ac2(0x1b1)+'d']);_0x2929a5[_0x254a6c(0x37d)+_0x254a6c(0x1f9)](_0xe6ab6b,_0xa9162c),_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'ok':!![],'note':_0x254a6c(0x170)+_0x310ac2(0x12e)+_0x310ac2(0x363)+'.'}));}catch(_0x399444){_0x428d66[_0x254a6c(0x34d)]=0x23e7+0xbd9*0x3+-0x45e2,_0x428d66['end'](JSON['stringify']({'error':_0x399444 instanceof Error?_0x399444[_0x254a6c(0x350)]:String(_0x399444)}));}return;}if(_0x1ec270==='/api/valid'+'ate-bot-to'+_0x254a6c(0x1d5)&&_0xbb0ab0['method']===_0x254a6c(0x123)){try{const {token:_0x2a14b2}=JSON[_0x254a6c(0x302)](_0x15594d);if(!_0x2a14b2||!_0x2a14b2[_0x310ac2(0x333)](':')){_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'ok':![],'error':_0x310ac2(0x227)+_0x310ac2(0x10b)}));return;}const _0x17b24b=await fetch(_0x310ac2(0x372)+_0x254a6c(0x32a)+_0x310ac2(0x27e)+_0x2a14b2+_0x254a6c(0x254)),_0x40f739=await _0x17b24b['json']();_0x40f739['ok']?_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'ok':!![],'bot':{'username':_0x40f739[_0x254a6c(0x1e8)][_0x310ac2(0x129)],'firstName':_0x40f739[_0x254a6c(0x1e8)][_0x254a6c(0x17a)],'id':_0x40f739[_0x310ac2(0x1e8)]['id']}})):_0x428d66[_0x310ac2(0x10f)](JSON['stringify']({'ok':![],'error':_0x40f739['descriptio'+'n']||'Invalid\x20to'+_0x254a6c(0x1d5)}));}catch(_0x4ff30e){_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'ok':![],'error':_0x4ff30e instanceof Error?_0x4ff30e[_0x310ac2(0x350)]:String(_0x4ff30e)}));}return;}if(_0x1ec270===_0x310ac2(0x29d)+'s'){let _0x310595={'name':_0x254a6c(0x1da)+_0x254a6c(0x1ad),'model':'none','status':'unconfigur'+'ed'};try{const _0x552636=getRegistry(),_0xef15ba=_0x552636[_0x310ac2(0x139)]()['getInfo']();_0x310595={'name':_0xef15ba[_0x310ac2(0x2f2)],'model':_0xef15ba['model'],'status':_0xef15ba[_0x254a6c(0x26e)]};}catch{}const _0x175c76=getMemoryStats(),_0xa9fd6f=getIndexStats(),_0x5ad516=getLoadedPlugins(),_0xdfc8cc=getMCPStatus(),_0x145895=listProfiles(),_0xa9db43=listCustomTools(),{getAllSessions:_0x4bde57}=await import('../service'+_0x310ac2(0x220)+'js'),_0x166736=_0x4bde57();let _0xca9dde=-0xbc2+-0x1*-0xf95+-0x1*0x3d3,_0x70de79=0x2f9*0x5+-0x17a6+-0xad*-0xd,_0x52851c=-0x762*-0x2+-0x1e2+-0xc2*0x11;for(const _0x13fe3d of _0x166736['values']()){_0xca9dde+=_0x13fe3d[_0x254a6c(0x347)+_0x254a6c(0x214)]||-0x1064+0x1aae+-0xa4a,_0x70de79+=_0x13fe3d[_0x254a6c(0x128)+_0x310ac2(0x14c)]||-0x1*0x204a+-0x3*0x350+-0x5e*-0x73,_0x52851c+=_0x13fe3d[_0x254a6c(0x18d)]||0x17*-0x13f+0x2087+-0x3de;}const {config:_0x1145fc}=await import(_0x310ac2(0x2fb)+'js');_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'bot':{'version':BOT_VERSION,'uptime':process[_0x310ac2(0x25a)]()},'model':_0x310595,'memory':{..._0x175c76,'vectors':_0xa9fd6f['entries'],'indexSize':_0xa9fd6f['sizeBytes']},'plugins':_0x5ad516[_0x310ac2(0x37f)],'mcp':_0xdfc8cc['length'],'users':_0x145895['length'],'tools':_0xa9db43[_0x254a6c(0x37f)],'tokens':{'totalInput':_0xca9dde,'totalOutput':_0x70de79,'total':_0xca9dde+_0x70de79,'totalCost':_0x52851c},'setup':{'telegram':!!_0x1145fc['botToken'],'provider':_0x310595[_0x254a6c(0x26e)]!==_0x254a6c(0x1b8)+'ed'}}));return;}if(_0x1ec270===_0x254a6c(0x1bc)+'s'){const _0x249c2c=getRegistry();_0x249c2c['listAll']()[_0x254a6c(0x348)](_0x371aa8=>{const _0x15a7c5=_0x254a6c;_0x428d66['end'](JSON['stringify']({'models':_0x371aa8,'active':_0x249c2c[_0x15a7c5(0x2c2)+'ey']()}));});return;}if(_0x1ec270===_0x254a6c(0x1bc)+_0x254a6c(0x319)&&_0xbb0ab0['method']===_0x310ac2(0x123)){try{const {key:_0x5e3061}=JSON['parse'](_0x15594d),_0x515d9d=getRegistry(),_0x400953=_0x515d9d[_0x254a6c(0x267)](_0x5e3061);_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'ok':_0x400953,'active':_0x515d9d[_0x254a6c(0x2c2)+'ey']()}));}catch{_0x428d66['statusCode']=0xce6*0x1+-0x1a58+0xf02,_0x428d66['end'](JSON['stringify']({'error':_0x254a6c(0x32e)+_0x254a6c(0x1bd)}));}return;}if(_0x1ec270===_0x254a6c(0x245)+_0x310ac2(0x2d5)&&_0xbb0ab0[_0x310ac2(0x159)]===_0x310ac2(0x34a)){try{const {getFallbackOrder:_0x5c6735}=await import(_0x254a6c(0x370)+_0x254a6c(0x27d)+_0x254a6c(0x1b0)),{getHealthStatus:_0x16040e,isFailedOver:_0x4dd238}=await import(_0x310ac2(0x370)+'s/heartbea'+_0x310ac2(0x314)),_0x19b752=getRegistry(),_0x129008=await _0x19b752[_0x254a6c(0x10c)]();_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'order':_0x5c6735(),'health':_0x16040e(),'failedOver':_0x4dd238(),'activeProvider':_0x19b752[_0x310ac2(0x2c2)+'ey'](),'availableProviders':_0x129008[_0x310ac2(0x1e6)](_0x1d2159=>({'key':_0x1d2159['key'],'name':_0x1d2159['name'],'status':_0x1d2159['status']}))}));}catch(_0x4d4fc0){_0x428d66['end'](JSON[_0x254a6c(0x13f)]({'error':String(_0x4d4fc0)}));}return;}if(_0x1ec270===_0x254a6c(0x245)+_0x254a6c(0x2d5)&&_0xbb0ab0[_0x310ac2(0x159)]===_0x310ac2(0x123)){try{const {primary:_0x2858dd,fallbacks:_0x522e5d}=JSON[_0x254a6c(0x302)](_0x15594d),{setFallbackOrder:_0x50beb0}=await import('../service'+_0x310ac2(0x27d)+_0x254a6c(0x1b0)),_0x5037d3=_0x50beb0(_0x2858dd,_0x522e5d,'webui');_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'ok':!![],'order':_0x5037d3}));}catch(_0x1dcade){_0x428d66[_0x310ac2(0x34d)]=0x1857+0x2551+-0x281*0x18,_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'error':String(_0x1dcade)}));}return;}if(_0x1ec270===_0x254a6c(0x245)+'ack/move'&&_0xbb0ab0[_0x310ac2(0x159)]==='POST'){try{const {key:_0x566f13,direction:_0x236fd3}=JSON[_0x254a6c(0x302)](_0x15594d),_0x29b13d=await import('../service'+'s/fallback'+_0x310ac2(0x1b0)),_0x507d09=_0x236fd3==='up'?_0x29b13d[_0x254a6c(0x2ad)](_0x566f13,_0x310ac2(0x345)):_0x29b13d[_0x254a6c(0x2ec)](_0x566f13,_0x254a6c(0x345));_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'ok':!![],'order':_0x507d09}));}catch(_0x1f3c9e){_0x428d66[_0x254a6c(0x34d)]=-0x2b*0xd3+-0x124*0x1+-0x23*-0x117,_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'error':String(_0x1f3c9e)}));}return;}if(_0x1ec270===_0x254a6c(0x19d)+_0x254a6c(0x368)){try{const {getHealthStatus:_0xc17af8,isFailedOver:_0x14ff3b}=await import(_0x254a6c(0x370)+_0x310ac2(0x329)+'t.js');_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'health':_0xc17af8(),'failedOver':_0x14ff3b()}));}catch(_0x7fb7f6){_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'health':[],'failedOver':![]}));}return;}if(_0x1ec270===_0x310ac2(0x32b)+'y'){const _0x2df598=loadLongTermMemory(),_0x7c8564=loadDailyLog(),_0x2ecf1b=getMemoryStats(),_0x24cfba=getIndexStats();let _0x5edb2e=[];try{_0x5edb2e=_0x2929a5['readdirSyn'+'c'](MEMORY_DIR)[_0x254a6c(0x380)](_0x48db55=>_0x48db55[_0x254a6c(0x18e)](_0x254a6c(0x327))&&!_0x48db55[_0x310ac2(0x1e0)]('.'))[_0x310ac2(0x199)]()[_0x310ac2(0x1c4)]();}catch{}_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'longTermMemory':_0x2df598,'todayLog':_0x7c8564,'dailyFiles':_0x5edb2e,'stats':_0x2ecf1b,'index':{'entries':_0x24cfba[_0x310ac2(0x320)],'files':_0x24cfba[_0x254a6c(0x321)],'sizeBytes':_0x24cfba['sizeBytes']}}));return;}if(_0x1ec270[_0x310ac2(0x1e0)](_0x254a6c(0x32b)+'y/')){const _0x3821b3=_0x1ec270['slice'](-0x18*0x12c+0x99*-0x1a+0x45f*0xa);if(_0x3821b3[_0x310ac2(0x333)]('..')||!_0x3821b3[_0x254a6c(0x18e)](_0x310ac2(0x327))){_0x428d66['statusCode']=-0x1865*0x1+0x1*0x49+0x19ac,_0x428d66['end'](JSON[_0x254a6c(0x13f)]({'error':_0x254a6c(0x1b6)+'le'}));return;}try{const _0x356f49=_0x2929a5['readFileSy'+'nc'](resolve(MEMORY_DIR,_0x3821b3),_0x254a6c(0x240));_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'file':_0x3821b3,'content':_0x356f49}));}catch{_0x428d66[_0x310ac2(0x34d)]=-0x643+-0x1098+0x186f,_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'error':'File\x20not\x20f'+_0x254a6c(0x100)}));}return;}if(_0x1ec270===_0x310ac2(0x32b)+'y/save'&&_0xbb0ab0['method']===_0x254a6c(0x123)){try{const {file:_0x5cc22a,content:_0xe0ab6b}=JSON[_0x254a6c(0x302)](_0x15594d);if(_0x5cc22a===_0x254a6c(0x1f0))_0x2929a5['writeFileS'+'ync'](MEMORY_FILE,_0xe0ab6b);else{if(_0x5cc22a[_0x310ac2(0x18e)]('.md')&&!_0x5cc22a[_0x310ac2(0x333)]('..'))_0x2929a5[_0x254a6c(0x37d)+'ync'](resolve(MEMORY_DIR,_0x5cc22a),_0xe0ab6b);else{_0x428d66['statusCode']=0xde9*0x1+0x6*-0x1dd+-0x17*0xd,_0x428d66['end'](JSON['stringify']({'error':'Invalid\x20fi'+'le'}));return;}}_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'ok':!![]}));}catch{_0x428d66['statusCode']=0xb14+-0x72a+-0xe*0x2b,_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':'Invalid\x20re'+_0x310ac2(0x1bd)}));}return;}if(_0x1ec270==='/api/plugi'+'ns'){_0x428d66[_0x310ac2(0x10f)](JSON['stringify']({'plugins':getLoadedPlugins()}));return;}if(_0x1ec270===_0x310ac2(0x30c)+'paces'){try{const {listWorkspaces:_0x4d1263,getDefaultWorkspace:_0x1621f6}=await import(_0x254a6c(0x370)+_0x310ac2(0x35e)+_0x254a6c(0x195)),{getCostByWorkspace:_0x2f4f5f}=await import('../service'+'s/session.'+'js'),_0x5a6788=_0x2f4f5f(),_0x41ff5c=_0x4d1263(),_0xf3d99c=[_0x1621f6(),..._0x41ff5c],_0x482322=_0xf3d99c[_0x310ac2(0x1e6)](_0x7b0b2a=>({'name':_0x7b0b2a[_0x310ac2(0x2f2)],'purpose':_0x7b0b2a[_0x310ac2(0x173)],'emoji':_0x7b0b2a[_0x254a6c(0x24e)]??null,'color':_0x7b0b2a[_0x254a6c(0x19e)]??null,'cwd':_0x7b0b2a['cwd'],'channels':_0x7b0b2a[_0x310ac2(0x24a)],'stats':_0x5a6788[_0x7b0b2a[_0x254a6c(0x2f2)]]??{'totalCost':0x0,'sessionCount':0x0,'messageCount':0x0,'toolUseCount':0x0}}));_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'workspaces':_0x482322}));}catch(_0x198a26){_0x428d66[_0x254a6c(0x34d)]=0x21a0+-0x15e5+-0x9c7,_0x428d66['end'](JSON['stringify']({'error':_0x198a26 instanceof Error?_0x198a26[_0x254a6c(0x350)]:String(_0x198a26)}));}return;}if(_0x1ec270===_0x254a6c(0x135)&&_0xbb0ab0[_0x310ac2(0x159)]===_0x310ac2(0x34a)){const {getAllSessions:_0x3d31a2}=await import(_0x254a6c(0x370)+_0x254a6c(0x220)+'js'),_0x4805b7=listProfiles(),_0x197fea=_0x3d31a2(),_0x41f1f9=new Map(Array[_0x310ac2(0x213)](_0x197fea[_0x254a6c(0x320)]())[_0x310ac2(0x1e6)](([_0x41e97f,_0x45f10b])=>[Number(_0x41e97f),_0x45f10b])),_0x59a403=_0x4805b7[_0x310ac2(0x1e6)](_0x18ed53=>{const _0x5af274=_0x254a6c,_0x505c69=_0x254a6c,_0x5c66c1=_0x41f1f9[_0x5af274(0x1e4)](_0x18ed53['userId']);return{..._0x18ed53,'session':_0x5c66c1?{'isProcessing':_0x5c66c1['isProcessi'+'ng'],'totalCost':_0x5c66c1[_0x5af274(0x18d)],'historyLength':_0x5c66c1[_0x5af274(0x34e)][_0x5af274(0x37f)],'effort':_0x5c66c1['effort'],'voiceReply':_0x5c66c1[_0x5af274(0x166)],'startedAt':_0x5c66c1[_0x5af274(0x208)],'messageCount':_0x5c66c1['messageCou'+'nt'],'toolUseCount':_0x5c66c1[_0x505c69(0x2ef)+'nt'],'workingDir':_0x5c66c1[_0x505c69(0x256)],'hasActiveQuery':!!_0x5c66c1[_0x505c69(0x113)+'oller'],'queuedMessages':_0x5c66c1[_0x505c69(0x36d)+'ue'][_0x505c69(0x37f)]}:null};});_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'users':_0x59a403}));return;}if(_0x1ec270['startsWith'](_0x254a6c(0x135)+'/')&&_0xbb0ab0[_0x254a6c(0x159)]==='DELETE'){if(isExposedWithoutPassword()){_0x428d66[_0x254a6c(0x34d)]=0x2a1*-0x1+-0xe06+0x1*0x123a,_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'error':_0x310ac2(0x160)+_0x254a6c(0x25d)+_0x310ac2(0x33b)+_0x254a6c(0x296)+'D'}));return;}const _0x2bfea3=parseInt(_0x1ec270['split']('/')['pop']()||'0');if(!_0x2bfea3){_0x428d66['statusCode']=0x2*-0x128b+-0x5a7*0x5+0x42e9,_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'error':_0x310ac2(0x1dd)+_0x254a6c(0x34f)}));return;}const {deleteUser:_0x304e45}=await import(_0x310ac2(0x370)+_0x254a6c(0x2da)),_0x59ad01=_0x304e45(_0x2bfea3);_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'ok':!![],..._0x59ad01}));return;}if(_0x1ec270===_0x254a6c(0x2d3)){const _0x53bb09=getCustomTools();_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'tools':_0x53bb09}));return;}if(_0x1ec270===_0x310ac2(0x2d3)+_0x310ac2(0x301)&&_0xbb0ab0['method']===_0x310ac2(0x123)){try{const {name:_0x3bc3ef,params:_0x14ff75}=JSON['parse'](_0x15594d);if(!_0x3bc3ef){_0x428d66['statusCode']=-0x1*0x2076+-0x369+-0x559*-0x7,_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'error':'No\x20tool\x20na'+'me'}));return;}const _0x4b2bce=await executeCustomTool(_0x3bc3ef,_0x14ff75||{});_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'ok':!![],'output':_0x4b2bce}));}catch(_0x29ea6e){const _0x1502ef=_0x29ea6e instanceof Error?_0x29ea6e['message']:String(_0x29ea6e);_0x428d66['end'](JSON[_0x254a6c(0x13f)]({'error':_0x1502ef}));}return;}if(_0x1ec270===_0x254a6c(0x28f)){const {getMCPStatus:_0x25e3ea,getMCPTools:_0x32c1e0,hasMCPConfig:_0xa6adbd}=await import(_0x254a6c(0x370)+_0x254a6c(0x204)),_0x86f294=_0x25e3ea(),_0x40eab9=_0x32c1e0(),_0x1551cf=MCP_CONFIG;let _0x32d835={'servers':{}};try{_0x32d835=JSON['parse'](_0x2929a5[_0x254a6c(0x271)+'nc'](_0x1551cf,_0x310ac2(0x240)));}catch{}_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'servers':_0x86f294,'tools':_0x40eab9,'config':_0x32d835,'hasConfig':_0xa6adbd()}));return;}if(_0x1ec270==='/api/mcp/a'+'dd'&&_0xbb0ab0[_0x310ac2(0x159)]===_0x310ac2(0x123)){try{const {name:_0x1801e4,command:_0x4f0c34,args:_0x4da155,url:_0x41613e,env:_0x226587,headers:_0x1728b6}=JSON[_0x310ac2(0x302)](_0x15594d);if(!_0x1801e4){_0x428d66[_0x254a6c(0x34d)]=0x1*-0x2705+-0x2*0x1367+0x4f63,_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':_0x254a6c(0x2f3)+_0x310ac2(0x2b2)}));return;}const _0x29b00b=MCP_CONFIG;let _0x23d440={'servers':{}};try{_0x23d440=JSON[_0x254a6c(0x302)](_0x2929a5['readFileSy'+'nc'](_0x29b00b,_0x254a6c(0x240)));}catch{}const _0x2c3b7={};if(_0x4f0c34){_0x2c3b7['command']=_0x4f0c34,_0x2c3b7[_0x254a6c(0x231)]=_0x4da155||[];if(_0x226587)_0x2c3b7[_0x310ac2(0x1c8)]=_0x226587;}else{if(_0x41613e){_0x2c3b7['url']=_0x41613e;if(_0x1728b6)_0x2c3b7['headers']=_0x1728b6;}else{_0x428d66['statusCode']=0x1*-0x1c1d+-0x209e+0x3e4b,_0x428d66['end'](JSON['stringify']({'error':'command\x20or'+_0x254a6c(0x182)+_0x254a6c(0x2b2)}));return;}}_0x23d440[_0x254a6c(0x269)][_0x1801e4]=_0x2c3b7,_0x2929a5[_0x310ac2(0x37d)+'ync'](_0x29b00b,JSON[_0x254a6c(0x13f)](_0x23d440,null,-0x408+-0x1*-0x14a1+-0x89*0x1f)),_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'ok':!![],'note':_0x310ac2(0x15c)+'eded\x20to\x20co'+_0x254a6c(0x167)}));}catch(_0x1a71a2){_0x428d66['statusCode']=0xe31+0x1683+0x1192*-0x2,_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':_0x1a71a2 instanceof Error?_0x1a71a2[_0x254a6c(0x350)]:String(_0x1a71a2)}));}return;}if(_0x1ec270===_0x254a6c(0x353)+'emove'&&_0xbb0ab0['method']==='POST'){try{const {name:_0x2e4247}=JSON[_0x254a6c(0x302)](_0x15594d),_0x28036a=MCP_CONFIG;let _0x5be068={'servers':{}};try{_0x5be068=JSON[_0x254a6c(0x302)](_0x2929a5[_0x310ac2(0x271)+'nc'](_0x28036a,_0x254a6c(0x240)));}catch{}delete _0x5be068['servers'][_0x2e4247],_0x2929a5[_0x254a6c(0x37d)+_0x310ac2(0x1f9)](_0x28036a,JSON[_0x254a6c(0x13f)](_0x5be068,null,0x411+-0x4b3+0x2*0x52)),_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'ok':!![]}));}catch(_0x2754b0){_0x428d66[_0x254a6c(0x34d)]=-0x16ae+0x33d*-0x1+0x69*0x43,_0x428d66['end'](JSON['stringify']({'error':_0x2754b0 instanceof Error?_0x2754b0[_0x310ac2(0x350)]:String(_0x2754b0)}));}return;}if(_0x1ec270==='/api/mcp/d'+_0x310ac2(0x1f7)){const _0x2d4bf9=[],{execSync:_0x46362d}=await import(_0x310ac2(0x35d)+_0x254a6c(0x23b)),_0x44b129=[{'pkg':'@modelcont'+_0x254a6c(0x28c)+_0x254a6c(0x21b)+'ilesystem','name':_0x254a6c(0x346),'args':[_0x310ac2(0x2fe)]},{'pkg':'@modelcont'+'extprotoco'+_0x310ac2(0x359)+_0x254a6c(0x18f)+'h','name':'brave-sear'+'ch','args':[]},{'pkg':_0x310ac2(0x290)+_0x254a6c(0x28c)+'l/server-g'+_0x310ac2(0x317),'name':_0x310ac2(0x354),'args':[]},{'pkg':_0x310ac2(0x290)+_0x310ac2(0x28c)+'l/server-p'+_0x310ac2(0x2bb),'name':_0x310ac2(0x1b4),'args':[]},{'pkg':_0x254a6c(0x290)+'extprotoco'+'l/server-s'+_0x310ac2(0x22a),'name':_0x310ac2(0x236),'args':[]},{'pkg':_0x254a6c(0x290)+_0x310ac2(0x28c)+_0x310ac2(0x248)+_0x254a6c(0x19b),'name':'slack','args':[]},{'pkg':'@modelcont'+_0x310ac2(0x28c)+'l/server-m'+_0x254a6c(0x125),'name':_0x310ac2(0x21f),'args':[]},{'pkg':_0x310ac2(0x290)+_0x254a6c(0x28c)+_0x310ac2(0x17e)+_0x254a6c(0x106),'name':_0x254a6c(0x30e),'args':[]},{'pkg':'@modelcont'+_0x254a6c(0x28c)+_0x254a6c(0x21b)+_0x254a6c(0x1d1),'name':_0x310ac2(0x1a6),'args':[]},{'pkg':_0x254a6c(0x112)+_0x254a6c(0x361)+_0x254a6c(0x11e)+'al-thinkin'+'g','name':'sequential'+_0x310ac2(0x246),'args':[]}];for(const _0xe76036 of _0x44b129){try{_0x46362d(_0x310ac2(0x2c0)+_0xe76036[_0x310ac2(0x289)]+_0x254a6c(0x2dd),{'timeout':0x1388,'stdio':_0x310ac2(0x339),'env':{...process[_0x254a6c(0x1c8)],'PATH':process[_0x254a6c(0x1c8)][_0x310ac2(0x31b)]+(':/opt/home'+_0x310ac2(0x29c)+_0x254a6c(0x1f2)+_0x310ac2(0x119))}}),_0x2d4bf9[_0x254a6c(0x305)]({'name':_0xe76036[_0x254a6c(0x2f2)],'command':_0x310ac2(0x179),'args':['-y',_0xe76036[_0x310ac2(0x289)],..._0xe76036[_0x254a6c(0x231)]],'source':'npm'});}catch{try{_0x46362d(_0x254a6c(0x23e)+'g\x20'+_0xe76036[_0x310ac2(0x289)]+'\x20--depth=0',{'timeout':0x1388,'stdio':_0x254a6c(0x339)}),_0x2d4bf9[_0x310ac2(0x305)]({'name':_0xe76036[_0x310ac2(0x2f2)],'command':'npx','args':['-y',_0xe76036[_0x310ac2(0x289)],..._0xe76036['args']],'source':_0x254a6c(0x1ea)});}catch{}}}const _0x2ec3d6=process[_0x254a6c(0x1c8)][_0x254a6c(0x1d0)]||process['env']['USERPROFIL'+'E']||'',_0x2cb9aa=[resolve(_0x2ec3d6,'.config/cl'+_0x310ac2(0x229)+_0x310ac2(0x1f5)+_0x310ac2(0x2a1)+'n'),resolve(_0x2ec3d6,_0x310ac2(0x1a0)+_0x310ac2(0x35b)+_0x254a6c(0x2a8)+_0x254a6c(0x229)+_0x310ac2(0x1f5)+_0x310ac2(0x2a1)+'n'),resolve(_0x2ec3d6,_0x310ac2(0x138)+'aming/Clau'+_0x310ac2(0x1d8)+'desktop_co'+'nfig.json')];for(const _0x238772 of _0x2cb9aa){try{const _0x19b812=JSON[_0x254a6c(0x302)](_0x2929a5['readFileSy'+'nc'](_0x238772,'utf-8'));if(_0x19b812[_0x254a6c(0x1cb)])for(const [_0x47a627,_0x46b17a]of Object['entries'](_0x19b812['mcpServers'])){_0x46b17a['command']&&_0x2d4bf9[_0x310ac2(0x305)]({'name':_0x310ac2(0x278)+_0x47a627,'command':_0x46b17a['command'],'args':_0x46b17a[_0x254a6c(0x231)]||[],'source':_0x254a6c(0x22f)+_0x310ac2(0x2a0)});}}catch{}}_0x428d66['end'](JSON[_0x254a6c(0x13f)]({'discovered':_0x2d4bf9}));return;}if(_0x1ec270?.[_0x310ac2(0x216)](/^\/api\/skills\/detail\//)&&_0xbb0ab0[_0x310ac2(0x159)]==='GET'){const _0x2b007b=_0x1ec270[_0x310ac2(0x27a)]('/')[_0x310ac2(0x1de)](),{getSkills:_0x439167}=await import('../service'+'s/skills.j'+'s'),_0x5b2ac4=_0x439167()[_0x310ac2(0x35a)](_0x1bde4e=>_0x1bde4e['id']===_0x2b007b);_0x5b2ac4?_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'ok':!![],'skill':_0x5b2ac4})):(_0x428d66[_0x310ac2(0x34d)]=0x1*-0x157f+0x14aa+0x269,_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'error':_0x310ac2(0x330)+_0x310ac2(0x1b7)})));return;}if(_0x1ec270===_0x310ac2(0x284)+_0x310ac2(0x252)&&_0xbb0ab0[_0x310ac2(0x159)]===_0x254a6c(0x123)){try{const {id:_0x43bcfb,name:_0x426787,description:_0x5859c5,triggers:_0x344520,category:_0x35c918,content:_0x11eece,priority:_0x394f20}=JSON['parse'](_0x15594d);if(!_0x43bcfb||!_0x426787){_0x428d66[_0x310ac2(0x34d)]=-0x1*0x1e71+-0xa5f*-0x2+0x3*0x3c1,_0x428d66['end'](JSON['stringify']({'error':_0x254a6c(0x211)+_0x310ac2(0x102)}));return;}if(typeof _0x43bcfb!==_0x254a6c(0x2c8)||_0x43bcfb[_0x254a6c(0x333)]('..')||_0x43bcfb[_0x310ac2(0x333)]('/')||_0x43bcfb[_0x310ac2(0x333)]('\x5c')||_0xd9de36['isAbsolute'](_0x43bcfb)){_0x428d66[_0x254a6c(0x34d)]=0x1622+-0xac4+-0x1f6*0x5,_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'error':_0x254a6c(0x190)+_0x254a6c(0x2e5)}));return;}const _0x163a1a=SKILLS_DIR,_0x3154ac=resolve(_0x163a1a,_0x43bcfb);if(!_0x3154ac[_0x254a6c(0x1e0)](_0x163a1a)){_0x428d66['statusCode']=-0x1bb9+-0x8*-0x335+0x3a1,_0x428d66[_0x310ac2(0x10f)](JSON['stringify']({'error':_0x254a6c(0x190)+_0x254a6c(0x2e5)}));return;}if(!_0x2929a5[_0x254a6c(0x1c0)](_0x3154ac))_0x2929a5['mkdirSync'](_0x3154ac,{'recursive':!![]});const _0x8af016=['---',_0x254a6c(0x30a)+_0x426787,_0x5859c5?_0x310ac2(0x335)+'n:\x20'+_0x5859c5:'',_0x344520?_0x310ac2(0x2ab)+(Array['isArray'](_0x344520)?_0x344520[_0x254a6c(0x17b)](',\x20'):_0x344520):'',_0x254a6c(0x180)+(_0x394f20||-0x1*0x1487+-0x1596+0x1*0x2a20),_0x310ac2(0x322)+(_0x35c918||_0x310ac2(0x2ea)),_0x254a6c(0x279)][_0x310ac2(0x380)](Boolean)[_0x310ac2(0x17b)]('\x0a');_0x2929a5[_0x254a6c(0x37d)+_0x254a6c(0x1f9)](resolve(_0x3154ac,_0x254a6c(0x23a)),_0x8af016+'\x0a\x0a'+(_0x11eece||''));const {loadSkills:_0x19b180}=await import(_0x254a6c(0x370)+'s/skills.j'+'s');_0x19b180(),_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'ok':!![]}));}catch(_0x2ff3cd){_0x428d66[_0x254a6c(0x34d)]=-0x2530+-0xbb*-0x18+0x7*0x308,_0x428d66['end'](JSON[_0x254a6c(0x13f)]({'error':_0x2ff3cd instanceof Error?_0x2ff3cd[_0x310ac2(0x350)]:String(_0x2ff3cd)}));}return;}if(_0x1ec270==='/api/skill'+'s/update'&&_0xbb0ab0['method']===_0x254a6c(0x123)){try{const {id:_0x534c1d,content:_0xd391}=JSON[_0x254a6c(0x302)](_0x15594d);if(typeof _0x534c1d!==_0x254a6c(0x2c8)||_0x534c1d[_0x310ac2(0x333)]('..')||_0x534c1d['includes']('/')||_0x534c1d[_0x254a6c(0x333)]('\x5c')||_0xd9de36[_0x254a6c(0x1b5)](_0x534c1d)){_0x428d66['statusCode']=0x2*0x132a+-0x18e6+-0xbde,_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':'Invalid\x20sk'+_0x254a6c(0x2e5)}));return;}if(!resolve(SKILLS_DIR,_0x534c1d)[_0x310ac2(0x1e0)](SKILLS_DIR)){_0x428d66['statusCode']=-0x695+-0x33*0x4d+0x5df*0x4,_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'error':'Invalid\x20sk'+_0x254a6c(0x2e5)}));return;}const _0x12f4b5=resolve(SKILLS_DIR,_0x534c1d,_0x254a6c(0x23a));if(!_0x2929a5['existsSync'](_0x12f4b5)){const _0x239362=resolve(SKILLS_DIR,_0x534c1d+_0x254a6c(0x327));if(_0x2929a5[_0x254a6c(0x1c0)](_0x239362))_0x2929a5[_0x254a6c(0x37d)+_0x254a6c(0x1f9)](_0x239362,_0xd391);else{_0x428d66[_0x254a6c(0x34d)]=0x262+-0x1956+0x8*0x311,_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'error':_0x254a6c(0x330)+'found'}));return;}}else _0x2929a5[_0x310ac2(0x37d)+_0x254a6c(0x1f9)](_0x12f4b5,_0xd391);const {loadSkills:_0x217eab}=await import(_0x254a6c(0x370)+_0x254a6c(0x2b9)+'s');_0x217eab(),_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'ok':!![]}));}catch(_0x496db0){_0x428d66[_0x310ac2(0x34d)]=0x103c+-0x2157+-0x3b*-0x51,_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'error':_0x496db0 instanceof Error?_0x496db0[_0x310ac2(0x350)]:String(_0x496db0)}));}return;}if(_0x1ec270===_0x254a6c(0x284)+_0x254a6c(0x12b)&&_0xbb0ab0['method']===_0x310ac2(0x123)){try{const {id:_0x43e6ac}=JSON['parse'](_0x15594d);if(typeof _0x43e6ac!=='string'||_0x43e6ac[_0x310ac2(0x333)]('..')||_0x43e6ac['includes']('/')||_0x43e6ac[_0x310ac2(0x333)]('\x5c')||_0xd9de36['isAbsolute'](_0x43e6ac)){_0x428d66['statusCode']=0xc43+0x1537+0xff5*-0x2,_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':'Invalid\x20sk'+_0x254a6c(0x2e5)}));return;}const _0x1d61cb=resolve(SKILLS_DIR,_0x43e6ac),_0x30ffee=resolve(SKILLS_DIR,_0x43e6ac+_0x310ac2(0x327));if(_0x2929a5[_0x310ac2(0x1c0)](_0x1d61cb))_0x2929a5[_0x310ac2(0x188)](_0x1d61cb,{'recursive':!![]});else{if(_0x2929a5[_0x310ac2(0x1c0)](_0x30ffee))_0x2929a5[_0x254a6c(0x14a)](_0x30ffee);else{_0x428d66[_0x254a6c(0x34d)]=0x1a32+0x16aa+-0x4*0xbd2,_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'error':_0x254a6c(0x330)+'found'}));return;}}const {loadSkills:_0x5e81a5}=await import(_0x254a6c(0x370)+'s/skills.j'+'s');_0x5e81a5(),_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'ok':!![]}));}catch(_0x2ce149){_0x428d66['statusCode']=0x21e5+-0x9fb+-0x165a,_0x428d66['end'](JSON['stringify']({'error':_0x2ce149 instanceof Error?_0x2ce149[_0x254a6c(0x350)]:String(_0x2ce149)}));}return;}if(_0x1ec270===_0x310ac2(0x1fe)+'g'){_0x428d66[_0x310ac2(0x10f)](JSON['stringify']({'providers':config['fallbackPr'+_0x310ac2(0x2ca)],'primaryProvider':config[_0x310ac2(0x117)+_0x254a6c(0x14e)],'allowedUsers':config[_0x254a6c(0x318)+'rs'],'hasKeys':{'groq':!!config[_0x310ac2(0x1f1)]['groq'],'openai':!!config['apiKeys'][_0x310ac2(0x1ab)],'google':!!config[_0x310ac2(0x1f1)][_0x310ac2(0x2c3)],'nvidia':!!config[_0x254a6c(0x1f1)]['nvidia'],'openrouter':!!config[_0x310ac2(0x1f1)][_0x254a6c(0x31e)]}}));return;}if(_0x1ec270==='/api/sessi'+_0x310ac2(0x19f)){const _0x216961=getAllSessions(),_0xd88ea3=listProfiles(),_0x1355ab=Array['from'](_0x216961[_0x254a6c(0x320)]())[_0x310ac2(0x1e6)](([_0x239adf,_0x367c8a])=>{const _0x1f5115=_0x310ac2,_0x54a8ea=_0x310ac2,_0x6edbc3=Number(_0x239adf[_0x1f5115(0x27a)](':')['pop']()),_0x5a22fb=_0xd88ea3[_0x1f5115(0x35a)](_0x2f22ad=>_0x2f22ad[_0x54a8ea(0x336)]===_0x6edbc3);return{'userId':_0x239adf,'name':_0x5a22fb?.[_0x54a8ea(0x2f2)]||'User\x20'+_0x239adf,'username':_0x5a22fb?.[_0x54a8ea(0x129)],'messageCount':_0x367c8a[_0x54a8ea(0x2eb)+'nt'],'toolUseCount':_0x367c8a['toolUseCou'+'nt'],'totalCost':_0x367c8a[_0x1f5115(0x18d)],'totalInputTokens':_0x367c8a[_0x54a8ea(0x347)+_0x1f5115(0x214)]||-0x1*-0x1c87+0x25*-0x67+0x3*-0x48c,'totalOutputTokens':_0x367c8a[_0x54a8ea(0x128)+_0x54a8ea(0x14c)]||0x1eb*0xa+-0x2689+-0x135b*-0x1,'effort':_0x367c8a[_0x54a8ea(0x162)],'startedAt':_0x367c8a['startedAt'],'lastActivity':_0x367c8a[_0x54a8ea(0x12d)+'ty'],'historyLength':_0x367c8a[_0x54a8ea(0x34e)][_0x1f5115(0x37f)],'isProcessing':_0x367c8a[_0x1f5115(0x103)+'ng'],'provider':Object[_0x54a8ea(0x287)](_0x367c8a[_0x1f5115(0x291)+_0x54a8ea(0x373)])[_0x54a8ea(0x17b)](',\x20')||_0x54a8ea(0x2d9)};});_0x428d66[_0x310ac2(0x10f)](JSON['stringify']({'sessions':_0x1355ab}));return;}if(_0x1ec270[_0x310ac2(0x216)](/^\/api\/sessions\/\d+\/history$/)){const _0x57e374=parseInt(_0x1ec270[_0x310ac2(0x27a)]('/')[-0x1fc8+0x2*0x99d+0xc91]),_0x38541a=getSession(_0x57e374);_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'userId':_0x57e374,'history':_0x38541a['history'][_0x310ac2(0x1e6)](_0x1f63e7=>({'role':_0x1f63e7[_0x310ac2(0x2e1)],'content':_0x1f63e7['content'][_0x254a6c(0x124)](-0x142d*0x1+0x2181+0x355*-0x4,-0x48+0x215d+-0x1945)}))}));return;}if(_0x1ec270==='/api/files'){const _0x168b68=new URLSearchParams((_0xbb0ab0[_0x310ac2(0x181)]||'')[_0x310ac2(0x27a)]('?')[0x17fd+0x2014+-0x3810]||''),_0x386507=_0x168b68[_0x310ac2(0x1e4)](_0x310ac2(0x242))||'',_0x554243=resolve(BOT_ROOT,_0x386507||'.');if(!_0x554243['startsWith'](BOT_ROOT)){_0x428d66[_0x254a6c(0x34d)]=0xf9c+0x4*0x8e4+0x1*-0x3199,_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'error':_0x254a6c(0x1e3)+'ied'}));return;}try{const _0x36fccc=_0x2929a5[_0x310ac2(0x331)](_0x554243);if(_0x36fccc['isDirector'+'y']()){const _0x2de3f8=_0x2929a5[_0x310ac2(0x1b2)+'c'](_0x554243,{'withFileTypes':!![]})[_0x254a6c(0x380)](_0x14ba99=>!_0x14ba99[_0x310ac2(0x2f2)][_0x254a6c(0x1e0)]('.')&&_0x14ba99['name']!==_0x310ac2(0x133)+'es')[_0x310ac2(0x1e6)](_0x519889=>({'name':_0x519889['name'],'type':_0x519889[_0x254a6c(0x247)+'y']()?_0x254a6c(0x365):_0x310ac2(0x342),'size':_0x519889[_0x310ac2(0x116)]()?_0x2929a5['statSync'](resolve(_0x554243,_0x519889['name']))[_0x310ac2(0x1ec)]:-0x74*-0x40+-0x535+-0x17cb,'modified':_0x2929a5['statSync'](resolve(_0x554243,_0x519889['name']))['mtimeMs']}))[_0x254a6c(0x199)]((_0x129c21,_0x3ed771)=>{const _0x11ee1e=_0x310ac2,_0x4b87d4=_0x254a6c;if(_0x129c21['type']!==_0x3ed771[_0x11ee1e(0x11b)])return _0x129c21[_0x4b87d4(0x11b)]==='dir'?-(0xc51+-0x15da+0x6f*0x16):0xab5*-0x2+-0x2*0x1c5+0x18f5*0x1;return _0x129c21[_0x11ee1e(0x2f2)]['localeComp'+_0x11ee1e(0x251)](_0x3ed771['name']);});_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'path':_0x386507||'.','entries':_0x2de3f8}));}else{const _0x4b9721=_0xd9de36[_0x254a6c(0x309)](_0x554243)['toLowerCas'+'e'](),_0x312ec5=new Set([_0x310ac2(0x327),_0x310ac2(0x11f),_0x310ac2(0x23d),_0x254a6c(0x378),_0x254a6c(0x134),_0x254a6c(0x297),_0x310ac2(0x2ae),_0x310ac2(0x2e4),_0x310ac2(0x25b),_0x310ac2(0x1ce),_0x310ac2(0x1d3),_0x254a6c(0x19a),'.yml','.yaml',_0x310ac2(0x2e3),'.ini',_0x310ac2(0x2a6),_0x254a6c(0x223),'.env',_0x254a6c(0x2fa),_0x254a6c(0x235),_0x310ac2(0x20a),_0x254a6c(0x1f8),_0x310ac2(0x307),_0x254a6c(0x1eb),_0x254a6c(0x371),_0x310ac2(0x31d),_0x310ac2(0x148),_0x254a6c(0x2b7),'.c','.cpp','.h',_0x254a6c(0x1dc),_0x310ac2(0x313),_0x254a6c(0x249),'.sql',_0x254a6c(0x2a4),_0x254a6c(0x2d6),_0x254a6c(0x215)+'e',_0x310ac2(0x115),_0x310ac2(0x1fb)+_0x310ac2(0x1c1),_0x254a6c(0x154)+'fig',_0x310ac2(0x126)+'c',_0x310ac2(0x122),_0x254a6c(0x209),_0x310ac2(0x1d6),_0x254a6c(0x2b3),_0x254a6c(0x326),_0x310ac2(0x130),_0x310ac2(0x127),_0x254a6c(0x2a7),_0x310ac2(0x12a),_0x310ac2(0x107),_0x310ac2(0x31f),_0x310ac2(0x288),_0x254a6c(0x1ef),'.svelte',_0x254a6c(0x1d9)]),_0x553ce1=new Set(['dockerfile',_0x310ac2(0x349),_0x254a6c(0x2cb),_0x254a6c(0x277),_0x254a6c(0x196),_0x254a6c(0x149)+'e',_0x310ac2(0x189),_0x254a6c(0x34b),_0x254a6c(0x11a),_0x254a6c(0x24c),'license','licence',_0x254a6c(0x33c),'changelog',_0x254a6c(0x15a),'contributo'+'rs']),_0x303b65=_0xd9de36[_0x254a6c(0x28a)](_0x554243)[_0x254a6c(0x33a)+'e'](),_0x2887c6=_0x553ce1[_0x254a6c(0x25f)](_0x303b65),_0x23e842=_0x312ec5[_0x310ac2(0x25f)](_0x4b9721)||_0x2887c6||!_0x4b9721&&_0x36fccc[_0x310ac2(0x1ec)]<0x1a6c1+0x1*-0x21196+0x1f175;if(_0x36fccc[_0x310ac2(0x1ec)]>0x1fca6+0x71959+-0x174df)_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'path':_0x386507,'content':_0x254a6c(0x340)+_0x310ac2(0x337)+(_0x36fccc[_0x310ac2(0x1ec)]/(0x1abc+-0x1d7a+-0x6be*-0x1))[_0x254a6c(0x21a)](-0x3*-0xc33+0x17*-0x157+-0x5c7)+('\x20KB\x20—\x20max\x20'+_0x310ac2(0x2ba)),'size':_0x36fccc[_0x254a6c(0x1ec)]}));else{if(_0x23e842)try{const _0x36cfe9=_0x2929a5[_0x254a6c(0x271)+'nc'](_0x554243,'utf-8'),_0x151290=[..._0x36cfe9[_0x310ac2(0x124)](0x14f1+-0x7*-0x482+0x1*-0x347f,0x871*-0x1+-0x4*-0x182+0x31*0x21)]['filter'](_0x269896=>_0x269896==='\x00')[_0x254a6c(0x37f)];_0x151290>-0xcff+-0x2f*0x40+0x1923?_0x428d66['end'](JSON[_0x254a6c(0x13f)]({'path':_0x386507,'content':null,'size':_0x36fccc[_0x310ac2(0x1ec)],'binary':!![]})):_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'path':_0x386507,'content':_0x36cfe9,'size':_0x36fccc[_0x254a6c(0x1ec)]}));}catch{_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'path':_0x386507,'content':null,'size':_0x36fccc['size'],'binary':!![]}));}else _0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'path':_0x386507,'content':null,'size':_0x36fccc[_0x254a6c(0x1ec)],'binary':!![]}));}}}catch{_0x428d66['statusCode']=-0x1efd+0x14a1+0x8*0x17e,_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'error':_0x254a6c(0x1f6)}));}return;}if(_0x1ec270===_0x254a6c(0x230)+_0x254a6c(0x1af)&&_0xbb0ab0['method']==='POST'){try{const {path:_0x1e15d1,content:_0x4b91e9}=JSON['parse'](_0x15594d),_0x4a390d=resolve(BOT_ROOT,_0x1e15d1);if(!_0x4a390d[_0x310ac2(0x1e0)](BOT_ROOT)){_0x428d66['statusCode']=0x2*-0x45b+-0x7*-0x448+0x1*-0x13af,_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'error':'Access\x20den'+_0x310ac2(0x168)}));return;}_0x2929a5[_0x310ac2(0x37d)+_0x254a6c(0x1f9)](_0x4a390d,_0x4b91e9),_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'ok':!![]}));}catch(_0x26114d){_0x428d66['statusCode']=-0xbb*-0x7+0x28*0x2+-0x3dd;const _0x45aad4=_0x26114d instanceof Error?_0x26114d[_0x310ac2(0x350)]:'Invalid\x20re'+'quest';_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'error':_0x45aad4}));}return;}if(_0x1ec270===_0x310ac2(0x230)+'/delete'&&_0xbb0ab0[_0x310ac2(0x159)]===_0x254a6c(0x123)){try{const {path:_0x22d99d}=JSON[_0x254a6c(0x302)](_0x15594d),_0x28aefd=resolve(BOT_ROOT,_0x22d99d);if(!_0x28aefd[_0x254a6c(0x1e0)](BOT_ROOT)){_0x428d66[_0x254a6c(0x34d)]=-0x828+-0x1ffd+0x29b8,_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'error':'Access\x20den'+'ied'}));return;}const _0x19c31f=[_0x310ac2(0x274),_0x310ac2(0x142)+'on',_0x254a6c(0x111)+_0x310ac2(0x1f3),'ecosystem.'+_0x310ac2(0x2cd)],_0x25872f=_0xd9de36[_0x310ac2(0x28a)](_0x28aefd);if(_0x19c31f[_0x254a6c(0x333)](_0x25872f)){_0x428d66[_0x310ac2(0x34d)]=-0x120a+-0x35e+-0x16fb*-0x1,_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'error':_0x25872f+(_0x254a6c(0x172)+'\x20deleted\x20('+'protected)')}));return;}if(!_0x2929a5[_0x310ac2(0x1c0)](_0x28aefd)){_0x428d66['statusCode']=0xfbb+0x22c2+-0x30e9,_0x428d66[_0x310ac2(0x10f)](JSON['stringify']({'error':_0x310ac2(0x36b)+_0x310ac2(0x100)}));return;}const _0x7d237b=_0x2929a5[_0x310ac2(0x331)](_0x28aefd);if(_0x7d237b['isDirector'+'y']()){_0x428d66[_0x254a6c(0x34d)]=-0xd71+0x2573*-0x1+0x3474,_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'error':_0x310ac2(0x310)+'s\x20cannot\x20b'+'e\x20deleted'}));return;}_0x2929a5[_0x254a6c(0x14a)](_0x28aefd),_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'ok':!![]}));}catch(_0x582754){_0x428d66[_0x310ac2(0x34d)]=0x1b81*0x1+-0x1bb+-0x1836*0x1;const _0x43a911=_0x582754 instanceof Error?_0x582754['message']:_0x254a6c(0x32e)+_0x310ac2(0x1bd);_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':_0x43a911}));}return;}if(_0x1ec270==='/api/termi'+_0x310ac2(0x1c6)&&_0xbb0ab0[_0x310ac2(0x159)]===_0x254a6c(0x123)){try{const {command:_0x22defd}=JSON['parse'](_0x15594d);if(!_0x22defd){_0x428d66[_0x310ac2(0x34d)]=0x1f94+-0x941*-0x1+0xd17*-0x3,_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'error':_0x310ac2(0x1aa)}));return;}if(_0x22defd[_0x310ac2(0x37f)]>-0x13*0x296+0xff*-0x2b+0x8307){_0x428d66[_0x310ac2(0x34d)]=-0x2675+0x1*-0x11d1+0x39d6*0x1,_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':'Command\x20to'+'o\x20long\x20(ma'+'x\x2010000\x20ch'+'ars)'}));return;}const _0x41f5a9=typeof JSON['parse'](_0x15594d)[_0x254a6c(0x192)]==='string'?resolve(JSON[_0x310ac2(0x302)](_0x15594d)[_0x310ac2(0x192)]):BOT_ROOT,_0x2101c2=execSync(_0x22defd,{'cwd':_0x41f5a9,'stdio':'pipe','timeout':0x1d4c0,'env':{...process[_0x310ac2(0x1c8)],'PATH':process['env'][_0x254a6c(0x31b)]+(_0x310ac2(0x25c)+_0x310ac2(0x29c)+'usr/local/'+'bin')}})[_0x310ac2(0x12c)]();_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'output':_0x2101c2[_0x254a6c(0x124)](0x1b7d+0x1082+-0x2bff,-0xe2a9+0x2*0x521e+0x1c50d*0x1)}));}catch(_0x108450){const _0x2b8ced=_0x108450,_0x3f5bec=_0x2b8ced['stderr']?.[_0x310ac2(0x12c)]()?.[_0x310ac2(0x1e7)]()||'';_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'output':_0x3f5bec||_0x2b8ced['message'],'exitCode':0x1}));}return;}if(_0x1ec270==='/api/env'){try{const _0x4cefa3=_0x2929a5[_0x310ac2(0x1c0)](ENV_FILE)?_0x2929a5['readFileSy'+'nc'](ENV_FILE,_0x254a6c(0x240)):'',_0x1a7b27=_0x4cefa3[_0x310ac2(0x27a)]('\x0a')['filter'](_0x144c80=>_0x144c80['includes']('=')&&!_0x144c80[_0x254a6c(0x1e0)]('#')),_0x24989d=_0x1a7b27['map'](_0x6d082a=>{const _0x23ecfc=_0x254a6c,_0x103b37=_0x254a6c,[_0x4208d2,..._0x40d3c8]=_0x6d082a['split']('='),_0x43c254=_0x40d3c8['join']('=')[_0x23ecfc(0x1e7)](),_0x4f486b=_0x4208d2[_0x23ecfc(0x333)]('KEY')||_0x4208d2[_0x23ecfc(0x333)](_0x103b37(0x2d8))||_0x4208d2['includes'](_0x23ecfc(0x140))||_0x4208d2[_0x23ecfc(0x333)]('SECRET')?_0x43c254['length']>0xf9c+0xd3b+0x1cd3*-0x1?_0x43c254[_0x23ecfc(0x124)](-0x6e3+0x4cb*0x1+-0x2*-0x10c,0x132e+-0x2394+0x106a)+'...'+_0x43c254[_0x103b37(0x124)](-(0x79f*0x3+0x4c9+0x189*-0x12)):_0x103b37(0x2dc):_0x43c254;return{'key':_0x4208d2[_0x23ecfc(0x1e7)](),'value':_0x4f486b,'hasValue':_0x43c254[_0x23ecfc(0x37f)]>-0x3d*-0x29+0x1012+0x1b9*-0xf};});_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'vars':_0x24989d}));}catch{_0x428d66['end'](JSON['stringify']({'vars':[]}));}return;}if(_0x1ec270===_0x310ac2(0x316)+'et'&&_0xbb0ab0[_0x254a6c(0x159)]===_0x254a6c(0x123)){try{const {key:_0x1a7bd9,value:_0x414b08}=JSON[_0x310ac2(0x302)](_0x15594d);if(!_0x1a7bd9||typeof _0x1a7bd9!==_0x254a6c(0x2c8)||!_0x1a7bd9[_0x254a6c(0x216)](/^[A-Z_][A-Z0-9_]*$/)){_0x428d66[_0x254a6c(0x34d)]=0x1f96+-0x2*0x8e+-0x1cea,_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':_0x254a6c(0x1fd)+_0x310ac2(0x1ee)}));return;}if(typeof _0x414b08===_0x254a6c(0x2c8)&&/[\n\r]/['test'](_0x414b08)){_0x428d66[_0x254a6c(0x34d)]=-0x1*0x1f9a+-0x14f3+0x361d,_0x428d66[_0x310ac2(0x10f)](JSON['stringify']({'error':_0x254a6c(0x2c4)+_0x310ac2(0x328)+_0x254a6c(0x357)+_0x310ac2(0x30f)+_0x254a6c(0x1ba)}));return;}let _0x3061eb=_0x2929a5[_0x310ac2(0x1c0)](ENV_FILE)?_0x2929a5[_0x310ac2(0x271)+'nc'](ENV_FILE,_0x310ac2(0x240)):'';const _0x5dc7e2=new RegExp('^'+_0x1a7bd9+'=.*$','m');_0x5dc7e2[_0x254a6c(0x352)](_0x3061eb)?_0x3061eb=_0x3061eb[_0x254a6c(0x2f6)](_0x5dc7e2,_0x1a7bd9+'='+_0x414b08):_0x3061eb=_0x3061eb[_0x254a6c(0x22e)]()+('\x0a'+_0x1a7bd9+'='+_0x414b08+'\x0a'),writeSecure(ENV_FILE,_0x3061eb),_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'ok':!![],'note':_0x254a6c(0x2e9)+'quired\x20for'+_0x254a6c(0x1a1)+_0x254a6c(0x253)+'ect'}));}catch{_0x428d66[_0x310ac2(0x34d)]=-0x9cd+0x180f+0x145*-0xa,_0x428d66['end'](JSON[_0x254a6c(0x13f)]({'error':'Invalid\x20re'+_0x254a6c(0x1bd)}));}return;}if(_0x1ec270==='/api/soul'){const _0x160fa2=getSoulContent();_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'content':_0x160fa2}));return;}if(_0x1ec270===_0x254a6c(0x276)+_0x310ac2(0x16d)&&_0xbb0ab0['method']==='POST'){try{const {content:_0x5aa558}=JSON[_0x254a6c(0x302)](_0x15594d),_0x5f3baa=SOUL_FILE;_0x2929a5[_0x254a6c(0x37d)+_0x310ac2(0x1f9)](_0x5f3baa,_0x5aa558),reloadSoul(),_0x428d66['end'](JSON[_0x254a6c(0x13f)]({'ok':!![]}));}catch{_0x428d66[_0x254a6c(0x34d)]=-0xa*0x21e+0x1bc*-0x8+0x249c,_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'error':_0x310ac2(0x32e)+_0x254a6c(0x1bd)}));}return;}if(_0x1ec270===_0x310ac2(0x202)+_0x254a6c(0x1e5)){const _0x3a266f=[{'name':_0x310ac2(0x2f1),'key':'BOT_TOKEN','icon':'📱','configured':!!process[_0x254a6c(0x1c8)][_0x310ac2(0x198)]},{'name':_0x254a6c(0x1ae),'key':'DISCORD_TO'+_0x310ac2(0x26a),'icon':'🎮','configured':!!process[_0x254a6c(0x1c8)][_0x310ac2(0x2a3)+'KEN']},{'name':_0x254a6c(0x1db),'key':_0x254a6c(0x268)+'NABLED','icon':'💬','configured':process[_0x254a6c(0x1c8)][_0x310ac2(0x268)+_0x254a6c(0x30b)]==='true'},{'name':'Signal','key':_0x254a6c(0x16a)+_0x254a6c(0x2f4),'icon':'🔒','configured':!!process[_0x254a6c(0x1c8)][_0x254a6c(0x16a)+_0x254a6c(0x2f4)]},{'name':_0x254a6c(0x224),'key':_0x254a6c(0x10e),'icon':'🌐','configured':!![]}];_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'platforms':_0x3a266f}));return;}if(_0x1ec270===_0x310ac2(0x27c)+'rt'&&_0xbb0ab0['method']===_0x254a6c(0x123)){const {scheduleGracefulRestart:_0x2bfa86}=await import(_0x254a6c(0x370)+_0x310ac2(0x1a5)+'js');_0x428d66[_0x310ac2(0x10f)](JSON['stringify']({'ok':!![],'note':_0x254a6c(0x18b)+_0x310ac2(0x2b6)})),_0x2bfa86(0x1*-0xf61+0xf73+0x1e2);return;}if(_0x1ec270===_0x310ac2(0x325)+_0x310ac2(0x1d7)&&_0xbb0ab0[_0x310ac2(0x159)]===_0x310ac2(0x123)){try{const {messages:_0x5777fa,format:_0x4867a4}=JSON['parse'](_0x15594d);if(_0x4867a4===_0x254a6c(0x255))_0x428d66[_0x254a6c(0x376)](_0x254a6c(0x341)+'pe',_0x310ac2(0x263)+_0x254a6c(0x24b)),_0x428d66['end'](JSON[_0x254a6c(0x13f)]({'export':_0x5777fa},null,-0x1814+0x10f9*0x1+0x25f*0x3));else{const _0xc4ad9a=_0x5777fa[_0x254a6c(0x1e6)](_0x5d7e54=>{const _0x53e470=_0x310ac2,_0x392073=_0x310ac2,_0x1aed9a=_0x5d7e54[_0x53e470(0x2e1)]===_0x53e470(0x2cf)?'**Du:**':_0x5d7e54[_0x392073(0x2e1)]===_0x392073(0x27f)?_0x53e470(0x164)+_0x392073(0x36c):'*System:*',_0x2cf888=_0x5d7e54[_0x53e470(0x19c)]?_0x53e470(0x324)+_0x5d7e54[_0x53e470(0x19c)]+')_':'';return''+_0x1aed9a+_0x2cf888+'\x0a'+_0x5d7e54[_0x53e470(0x362)]+'\x0a';})[_0x254a6c(0x17b)](_0x254a6c(0x37b));_0x428d66[_0x310ac2(0x376)]('Content-Ty'+'pe','text/markd'+_0x310ac2(0x280)),_0x428d66[_0x254a6c(0x10f)](_0x310ac2(0x20f)+'ort\x20—\x20Alvi'+_0x310ac2(0x155)+new Date()[_0x310ac2(0x2f5)+_0x310ac2(0x1bb)](_0x254a6c(0x11c))+_0x310ac2(0x2d1)+_0xc4ad9a);}}catch{_0x428d66[_0x310ac2(0x34d)]=0x395+-0x1fa6+-0x1*-0x1da1,_0x428d66[_0x254a6c(0x10f)](JSON[_0x310ac2(0x13f)]({'error':'Invalid\x20re'+_0x310ac2(0x1bd)}));}return;}if(_0x1ec270===_0x310ac2(0x15b)+_0x254a6c(0x137)&&_0xbb0ab0[_0x254a6c(0x159)]==='GET'){try{const {getWhatsAppAdapter:_0x1ab316}=await import('../platfor'+_0x310ac2(0x157)+_0x310ac2(0x294)),_0x544237=_0x1ab316();if(!_0x544237){_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'groups':[],'error':_0x254a6c(0x1fa)+'icht\x20verbu'+'nden'}));return;}const _0x447a15=await _0x544237[_0x310ac2(0x121)]();_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'groups':_0x447a15}));}catch(_0x2d8fcd){_0x428d66['end'](JSON[_0x254a6c(0x13f)]({'groups':[],'error':String(_0x2d8fcd)}));}return;}if(_0x1ec270[_0x254a6c(0x216)](/^\/api\/whatsapp\/groups\/[^/]+\/participants$/)){try{const _0x27f697=decodeURIComponent(_0x1ec270[_0x310ac2(0x27a)]('/')[-0x116*0x23+0x219c+-0x5*-0xe2]),{getWhatsAppAdapter:_0x3d41bc}=await import('../platfor'+_0x254a6c(0x157)+_0x310ac2(0x294)),_0x3c0ebe=_0x3d41bc();if(!_0x3c0ebe){_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'participants':[],'error':_0x254a6c(0x1fa)+'icht\x20verbu'+_0x310ac2(0x11d)}));return;}const _0x569d05=await _0x3c0ebe[_0x254a6c(0x35f)+_0x254a6c(0x308)](_0x27f697);_0x428d66[_0x310ac2(0x10f)](JSON[_0x310ac2(0x13f)]({'participants':_0x569d05}));}catch(_0x4aec0e){_0x428d66[_0x310ac2(0x10f)](JSON[_0x254a6c(0x13f)]({'participants':[],'error':String(_0x4aec0e)}));}return;}if(_0x1ec270==='/api/whats'+_0x310ac2(0x32d)+_0x310ac2(0x110)&&_0xbb0ab0[_0x254a6c(0x159)]===_0x254a6c(0x34a)){const {getGroupRules:_0x1fc91b}=await import(_0x310ac2(0x1cd)+_0x254a6c(0x157)+'p.js');_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'rules':_0x1fc91b()}));return;}if(_0x1ec270===_0x254a6c(0x15b)+_0x310ac2(0x32d)+_0x254a6c(0x110)&&_0xbb0ab0['method']===_0x310ac2(0x123)){try{const _0x5ce708=JSON[_0x310ac2(0x302)](_0x15594d);if(!_0x5ce708[_0x254a6c(0x20c)]){_0x428d66['statusCode']=0x251b+0x119*0xd+0x8*-0x63a,_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'error':'groupId\x20is'+'t\x20erforder'+_0x254a6c(0x311)}));return;}const {upsertGroupRule:_0x2a4264}=await import(_0x254a6c(0x1cd)+_0x254a6c(0x157)+_0x254a6c(0x294)),_0x211734=_0x2a4264(_0x5ce708);_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'ok':!![],'rule':_0x211734}));}catch(_0x577d8c){_0x428d66[_0x310ac2(0x34d)]=0x124a+-0x21b6+0x10fc,_0x428d66['end'](JSON[_0x310ac2(0x13f)]({'error':String(_0x577d8c)}));}return;}if(_0x1ec270['match'](/^\/api\/whatsapp\/group-rules\//)&&_0xbb0ab0[_0x310ac2(0x159)]==='DELETE'){if(isExposedWithoutPassword()){_0x428d66[_0x254a6c(0x34d)]=0x265d+-0x17d*0x4+-0x1ed6*0x1,_0x428d66[_0x254a6c(0x10f)](JSON[_0x254a6c(0x13f)]({'error':_0x254a6c(0x160)+_0x310ac2(0x25d)+_0x310ac2(0x33b)+_0x310ac2(0x296)+'D'}));return;}const _0x14d351=decodeURIComponent(_0x1ec270[_0x310ac2(0x27a)]('/')[_0x254a6c(0x124)](0x1*-0x17cf+0x8d7+0xefc)[_0x254a6c(0x17b)]('/')),{deleteGroupRule:_0x373d99}=await import('../platfor'+_0x310ac2(0x157)+'p.js'),_0x811f42=_0x373d99(_0x14d351);_0x428d66['end'](JSON['stringify']({'ok':_0x811f42}));return;}_0x428d66['statusCode']=0x1*0x2429+-0xa86+0x180f*-0x1,_0x428d66[_0x254a6c(0x10f)](JSON['stringify']({'error':_0x310ac2(0x1f6)}));}const chatClients=new Set();broadcast['on'](_0x217a38(0x332),_0x4a5fe9=>{const _0x410e44=_0x217a38,_0x113967=_0x3da471;if(_0x4a5fe9[_0x410e44(0x22d)]!==_0x113967(0x2d2))return;const _0x8802e6=JSON['stringify']({'type':'mirror:use'+_0x410e44(0x145),'text':_0x4a5fe9[_0x410e44(0x362)],'platform':_0x4a5fe9['platform'],'userName':_0x4a5fe9['userName'],'ts':_0x4a5fe9['ts']});for(const _0x313d5b of chatClients){if(_0x313d5b[_0x113967(0x1c5)]===WebSocket[_0x410e44(0x2de)])_0x313d5b[_0x113967(0x266)](_0x8802e6);}}),broadcast['on']('response_s'+'tart',_0x20b0de=>{const _0x42db7e=_0x3da471,_0x89c228=_0x217a38;if(_0x20b0de[_0x42db7e(0x22d)]!==_0x42db7e(0x2d2))return;const _0x117353=JSON[_0x89c228(0x13f)]({'type':'mirror:res'+_0x89c228(0x1ff)+'t','platform':_0x20b0de['platform'],'ts':_0x20b0de['ts']});for(const _0x2b4fbd of chatClients){if(_0x2b4fbd['readyState']===WebSocket[_0x89c228(0x2de)])_0x2b4fbd['send'](_0x117353);}}),broadcast['on'](_0x3da471(0x28d)+_0x217a38(0x1f4),_0x2f5b99=>{const _0x1873ac=_0x217a38,_0x3d4d51=_0x217a38;if(_0x2f5b99[_0x1873ac(0x22d)]!=='telegram')return;const _0x2b0ca7=JSON[_0x1873ac(0x13f)]({'type':_0x1873ac(0x13a)+_0x1873ac(0x1d4)+'a','delta':_0x2f5b99[_0x1873ac(0x293)],'platform':_0x2f5b99[_0x1873ac(0x22d)],'ts':_0x2f5b99['ts']});for(const _0x23efa2 of chatClients){if(_0x23efa2[_0x1873ac(0x1c5)]===WebSocket[_0x3d4d51(0x2de)])_0x23efa2[_0x3d4d51(0x266)](_0x2b0ca7);}}),broadcast['on'](_0x217a38(0x28d)+'one',_0x29d102=>{const _0xc066f=_0x217a38,_0x3e0985=_0x217a38;if(_0x29d102[_0xc066f(0x22d)]!==_0x3e0985(0x2d2))return;const _0xb26cf2=JSON[_0xc066f(0x13f)]({'type':'mirror:res'+_0x3e0985(0x225),'cost':_0x29d102[_0x3e0985(0x379)],'platform':_0x29d102[_0x3e0985(0x22d)],'ts':_0x29d102['ts']});for(const _0x5f50a2 of chatClients){if(_0x5f50a2[_0xc066f(0x1c5)]===WebSocket[_0x3e0985(0x2de)])_0x5f50a2[_0xc066f(0x266)](_0xb26cf2);}});function handleWebSocket(_0x51d436){_0x51d436['on']('connection',(_0x26769b,_0x331571)=>{const _0x5e5722=_0x4a44,_0x1c302d=_0x4a44;if(WEB_PASSWORD&&!checkAuth(_0x331571)){_0x26769b['close'](0x1d31+-0x4*0x64e+0xba8,_0x5e5722(0x2e0)+'ticated');return;}const _0x2ec326=_0x331571[_0x1c302d(0x181)]||'/';if(_0x2ec326===_0x1c302d(0x1c9)){addCanvasClient(_0x26769b);return;}console[_0x5e5722(0x2bf)](_0x1c302d(0x37c)+_0x1c302d(0x285)+_0x5e5722(0x234)),chatClients[_0x5e5722(0x2e2)](_0x26769b),_0x26769b['on'](_0x5e5722(0x350),async _0x17514a=>{const _0x5612a7=_0x1c302d,_0xd80e73=_0x1c302d;try{const _0x2e0383=JSON['parse'](_0x17514a[_0x5612a7(0x12c)]());if(_0x2e0383[_0xd80e73(0x11b)]===_0x5612a7(0x32f)){let {text:_0x27d8d6,effort:_0x581fa9,file:_0x4448b6}=_0x2e0383;const _0x2b5134=_0x2e0383[_0xd80e73(0x239)],_0x29c455=config[_0x5612a7(0x318)+'rs'][0x49*-0x3f+-0x2160+0x3357*0x1]||0x1*-0x2670+-0xf3e+0x35ae;let _0x219d19;if(_0x2b5134==='tui'&&typeof _0x2e0383[_0x5612a7(0x2c9)]===_0xd80e73(0x2c8)&&_0x2e0383[_0xd80e73(0x2c9)][_0x5612a7(0x1e0)](_0xd80e73(0x14d)))_0x219d19=_0x2e0383[_0x5612a7(0x2c9)];else _0x2b5134==='telegram'?_0x219d19=_0x29c455:_0x219d19=_0x29c455;if(_0x4448b6?.[_0xd80e73(0x109)]&&_0x4448b6?.[_0xd80e73(0x2f2)])try{const _0x269ec1=resolve(DATA_DIR,_0xd80e73(0x281)+'s');if(!_0x2929a5['existsSync'](_0x269ec1))_0x2929a5[_0xd80e73(0x2ed)](_0x269ec1,{'recursive':!![]});const _0x56cfce=_0x4448b6['name'][_0x5612a7(0x2f6)](/[^a-zA-Z0-9._-]/g,'_'),_0xb60c53=resolve(_0x269ec1,Date[_0x5612a7(0x315)]()+'_'+_0x56cfce),_0x55b051=_0x4448b6[_0xd80e73(0x109)][_0xd80e73(0x27a)](',')[0x23f8+0x1226*0x1+0x1*-0x361d]||_0x4448b6[_0x5612a7(0x109)];_0x2929a5[_0xd80e73(0x37d)+_0x5612a7(0x1f9)](_0xb60c53,Buffer[_0xd80e73(0x213)](_0x55b051,_0xd80e73(0x104))),_0x27d8d6=_0x27d8d6[_0x5612a7(0x2f6)](/\[File attached:.*?\]/,'[File\x20save'+_0x5612a7(0x2f9)+_0xb60c53+']');}catch(_0xd01a28){console['error'](_0x5612a7(0x2f8)+_0x5612a7(0x191)+_0xd80e73(0x193),_0xd01a28);}const _0x42acff=getRegistry(),_0x40e1ac=_0x42acff[_0x5612a7(0x139)](),_0x23400a=_0x40e1ac[_0x5612a7(0x1a4)][_0xd80e73(0x11b)]===_0x5612a7(0x187),_0x482c1a=getSession(_0x219d19),_0x1ebe5b={'prompt':_0x27d8d6,'systemPrompt':buildSystemPrompt(_0x23400a,_0x482c1a[_0xd80e73(0x2e7)],_0x2b5134===_0xd80e73(0x2d2)?'telegram':_0xd80e73(0x299)+_0x5612a7(0x232)),'workingDir':_0x482c1a['workingDir'],'effort':_0x581fa9||_0x482c1a[_0x5612a7(0x162)],'sessionId':_0x23400a?_0x482c1a[_0xd80e73(0x13e)]:null,'history':!_0x23400a?_0x482c1a['history']:undefined};let _0x439962=![],_0x2413bc='';try{for await(const _0x21d0c9 of _0x42acff[_0xd80e73(0x184)+_0xd80e73(0x244)](_0x1ebe5b)){if(_0x26769b['readyState']!==WebSocket['OPEN'])break;switch(_0x21d0c9[_0xd80e73(0x11b)]){case _0x5612a7(0x362):if(_0x21d0c9['text'])_0x2413bc=_0x21d0c9[_0xd80e73(0x362)];_0x26769b[_0x5612a7(0x266)](JSON['stringify']({'type':_0x5612a7(0x362),'text':_0x21d0c9[_0xd80e73(0x362)],'delta':_0x21d0c9[_0x5612a7(0x293)]}));break;case _0xd80e73(0x222):_0x26769b['send'](JSON[_0xd80e73(0x13f)]({'type':_0x5612a7(0x360),'name':_0x21d0c9[_0x5612a7(0x14f)],'input':_0x21d0c9['toolInput']}));break;case _0xd80e73(0x2cc):_0x439962=!![];if(_0x21d0c9['text'])_0x2413bc=_0x21d0c9[_0x5612a7(0x362)];if(_0x21d0c9[_0xd80e73(0x13e)])_0x482c1a['sessionId']=_0x21d0c9['sessionId'];if(_0x21d0c9[_0xd80e73(0x16b)])_0x482c1a[_0x5612a7(0x18d)]+=_0x21d0c9['costUsd'];if(_0x21d0c9['inputToken'+'s'])_0x482c1a[_0x5612a7(0x347)+'Tokens']=(_0x482c1a['totalInput'+_0x5612a7(0x214)]||0x1bf*0x6+0x17b2+-0x222c)+_0x21d0c9[_0xd80e73(0x165)+'s'];if(_0x21d0c9[_0xd80e73(0x23c)+'ns'])_0x482c1a[_0xd80e73(0x128)+_0xd80e73(0x14c)]=(_0x482c1a[_0xd80e73(0x128)+_0x5612a7(0x14c)]||0xca1+0x15df+-0x8*0x450)+_0x21d0c9[_0x5612a7(0x23c)+'ns'];_0x26769b['send'](JSON[_0xd80e73(0x13f)]({'type':_0xd80e73(0x2cc),'cost':_0x21d0c9['costUsd'],'sessionId':_0x21d0c9[_0xd80e73(0x13e)],'inputTokens':_0x21d0c9[_0x5612a7(0x165)+'s'],'outputTokens':_0x21d0c9[_0xd80e73(0x23c)+'ns'],'sessionTokens':{'input':_0x482c1a['totalInput'+_0xd80e73(0x214)]||0x2409+0x1*0x19a9+-0x1ed9*0x2,'output':_0x482c1a[_0xd80e73(0x128)+_0x5612a7(0x14c)]||0x67f+0x70*0x30+-0x1b7f}}));break;case _0x5612a7(0x26c):_0x26769b[_0x5612a7(0x266)](JSON[_0xd80e73(0x13f)]({'type':_0x5612a7(0x26c),'error':_0x21d0c9[_0xd80e73(0x26c)]})),_0x439962=!![];break;case'fallback':_0x26769b['send'](JSON[_0xd80e73(0x13f)]({'type':'fallback','from':_0x21d0c9['failedProv'+'ider'],'to':_0x21d0c9[_0x5612a7(0x1e9)+'me']}));break;}}!_0x439962&&_0x26769b[_0xd80e73(0x1c5)]===WebSocket[_0xd80e73(0x2de)]&&_0x26769b[_0xd80e73(0x266)](JSON[_0xd80e73(0x13f)]({'type':_0xd80e73(0x2cc),'cost':0x0}));if(_0x2b5134===_0x5612a7(0x2d2)&&_0x2413bc[_0xd80e73(0x1e7)]())try{const _0x5778b9=await import(_0xd80e73(0x370)+_0xd80e73(0x367)+_0xd80e73(0x23f));_0x5778b9['enqueue'](_0xd80e73(0x2d2),String(_0x29c455),_0x2413bc);}catch(_0x3d3f6f){console['error'](_0x5612a7(0x158)+'legram\x20rel'+_0xd80e73(0x2ac),_0x3d3f6f);}}catch(_0x1f3892){const _0x29a7cd=_0x1f3892 instanceof Error?_0x1f3892[_0xd80e73(0x350)]:String(_0x1f3892);console[_0xd80e73(0x26c)]('WebUI\x20stre'+_0xd80e73(0x306),_0x29a7cd),_0x26769b[_0xd80e73(0x1c5)]===WebSocket[_0x5612a7(0x2de)]&&(_0x26769b[_0x5612a7(0x266)](JSON[_0x5612a7(0x13f)]({'type':_0xd80e73(0x26c),'error':_0x29a7cd})),!_0x439962&&_0x26769b[_0x5612a7(0x266)](JSON[_0x5612a7(0x13f)]({'type':_0xd80e73(0x2cc),'cost':0x0})));}}if(_0x2e0383[_0xd80e73(0x11b)]===_0x5612a7(0x29b)){const _0x29e43d=_0x2e0383[_0xd80e73(0x239)],_0xdbd994=config['allowedUse'+'rs'][0x90d+0x1*-0x1d+-0x8f0]||0x22be+0x1ed0+-0x418e;let _0x23867c;_0x29e43d==='tui'&&typeof _0x2e0383[_0x5612a7(0x2c9)]===_0xd80e73(0x2c8)&&_0x2e0383['sessionKey'][_0x5612a7(0x1e0)](_0xd80e73(0x14d))?_0x23867c=_0x2e0383['sessionKey']:_0x23867c=_0xdbd994,resetSession(_0x23867c),_0x26769b['send'](JSON[_0x5612a7(0x13f)]({'type':_0x5612a7(0x29b),'ok':!![]}));}}catch(_0x3d798d){const _0x2cfc37=_0x3d798d instanceof Error?_0x3d798d[_0xd80e73(0x350)]:String(_0x3d798d);_0x26769b[_0xd80e73(0x266)](JSON[_0xd80e73(0x13f)]({'type':_0x5612a7(0x26c),'error':_0x2cfc37}));}}),_0x26769b['on']('close',()=>{const _0x3c3376=_0x1c302d,_0x38fde6=_0x1c302d;console[_0x3c3376(0x2bf)](_0x38fde6(0x37c)+_0x3c3376(0x2b0)+_0x38fde6(0x26b)),chatClients[_0x3c3376(0x1b9)](_0x26769b);});});}function handleWebRequest(_0x3214f2,_0x5b326e){const _0x29b4c1=_0x217a38,_0x1548c3=_0x217a38;let _0x3604a2='';_0x3214f2['on'](_0x29b4c1(0x275),_0x5a159a=>{_0x3604a2+=_0x5a159a;}),_0x3214f2['on'](_0x29b4c1(0x10f),()=>{const _0x5e0da9=_0x1548c3,_0x2fab8d=_0x29b4c1,_0x9dd217=(_0x3214f2[_0x5e0da9(0x181)]||'/')[_0x5e0da9(0x27a)]('?')[0x35d*0x4+0xbe*-0xe+-0x62*0x8];if(_0x9dd217[_0x5e0da9(0x1e0)](_0x2fab8d(0x243))){handleOpenAICompat(_0x3214f2,_0x5b326e,_0x9dd217,_0x3604a2);return;}if(_0x9dd217[_0x5e0da9(0x1e0)]('/api/')){handleAPI(_0x3214f2,_0x5b326e,_0x9dd217,_0x3604a2);return;}if(_0x9dd217[_0x2fab8d(0x1e0)](_0x5e0da9(0x1c3))){handleAPI(_0x3214f2,_0x5b326e,_0x9dd217,_0x3604a2);return;}if(WEB_PASSWORD&&!checkAuth(_0x3214f2)&&_0x9dd217!==_0x2fab8d(0x212)+'l'){_0x5b326e[_0x5e0da9(0x1cf)](0x893*-0x2+-0x7*0x577+0x3895,{'Location':_0x5e0da9(0x212)+'l'}),_0x5b326e[_0x2fab8d(0x10f)]();return;}if(_0x9dd217===_0x2fab8d(0x257)){const _0x43f7a6=resolve(PUBLIC_DIR,'canvas.htm'+'l');try{const _0x2970c3=_0x2929a5['readFileSy'+'nc'](_0x43f7a6);_0x5b326e[_0x2fab8d(0x376)](_0x2fab8d(0x341)+'pe',_0x5e0da9(0x2b8)),_0x5b326e[_0x5e0da9(0x10f)](_0x2970c3);}catch{_0x5b326e['statusCode']=-0xa17+0x7*0x21b+-0x312,_0x5b326e['end'](_0x5e0da9(0x1f6));}return;}let _0x4a797c=_0x9dd217==='/'?'/index.htm'+'l':_0x9dd217;_0x4a797c=resolve(PUBLIC_DIR,_0x4a797c['slice'](0x2a3+-0x1eed+0x1c4b));if(!_0x4a797c[_0x2fab8d(0x1e0)](PUBLIC_DIR)){_0x5b326e['statusCode']=-0x1*-0x1cb7+0x1919+0x343d*-0x1,_0x5b326e[_0x2fab8d(0x10f)](_0x2fab8d(0x323));return;}try{const _0x53e8aa=_0x2929a5[_0x2fab8d(0x271)+'nc'](_0x4a797c),_0x2a4884=_0xd9de36[_0x5e0da9(0x309)](_0x4a797c);_0x5b326e['setHeader'](_0x5e0da9(0x341)+'pe',MIME[_0x2a4884]||_0x2fab8d(0x263)+_0x5e0da9(0x375)+'ream'),_0x5b326e[_0x2fab8d(0x10f)](_0x53e8aa);}catch{_0x5b326e[_0x5e0da9(0x34d)]=-0x1*0x995+0x8*-0x16c+0x1689,_0x5b326e['end']('Not\x20found');}});}export function startWebServer(){const _0x2482ce=_0x3da471;stopRequested=![],scheduleBindAttempt(parseInt(process['env'][_0x2482ce(0x10e)]||'3100',-0x2*-0xb77+0x1e13*0x1+-0x34f7),-0x3*0x5f3+0x9e5*-0x1+-0x35*-0x86);}function scheduleBindAttempt(_0x390fac,_0x12d65b){const _0x292262=_0x3da471,_0x407f8f=_0x3da471;if(stopRequested)return;const _0x434b38=parseInt(process[_0x292262(0x1c8)][_0x407f8f(0x10e)]||_0x407f8f(0x18a)),_0xf8adf3=_0x547279[_0x407f8f(0x273)+'er'](handleWebRequest);let _0x1a0942=![];const _0x3818e4=()=>{const _0x29aa70=_0x407f8f,_0x3ed6a6=_0x407f8f;try{_0xf8adf3['removeAllL'+_0x29aa70(0x178)](_0x3ed6a6(0x26c));}catch{}try{_0xf8adf3['close'](()=>{});}catch{}},_0x26d422=_0x42866b=>{const _0x46790f=_0x407f8f,_0x1e6014=_0x292262;if(_0x1a0942)return;_0x1a0942=!![],_0x3818e4();if(stopRequested)return;const _0x131ff9=decideNextBindAction(_0x42866b,_0x12d65b,{'originalPort':_0x434b38,'maxPortTries':MAX_PORT_TRIES,'backgroundRetryMs':BACKGROUND_RETRY_MS});if(_0x131ff9[_0x46790f(0x11b)]==='retry-port'){console[_0x46790f(0x37a)](_0x1e6014(0x33e)+'\x20'+_0x390fac+_0x46790f(0x283)+(_0x42866b[_0x46790f(0x260)]||_0x42866b[_0x46790f(0x350)])+(_0x46790f(0x1bf)+'\x20')+_0x131ff9[_0x46790f(0x174)]),scheduleBindAttempt(_0x131ff9[_0x1e6014(0x174)],_0x131ff9[_0x46790f(0x210)]);return;}console['warn'](_0x1e6014(0x1e1)+_0x46790f(0x28b)+(_0x42866b[_0x1e6014(0x260)]||_0x42866b[_0x1e6014(0x350)])+')\x20—\x20'+('backing\x20of'+'f\x20'+_0x131ff9[_0x1e6014(0x303)]/(-0x281+-0x30*-0xa7+-0x18e7)+(_0x1e6014(0x377)+'rying\x20port'+'\x20')+_0x131ff9[_0x1e6014(0x174)]+'.\x20')+(_0x1e6014(0x24d)+'ffected;\x20T'+_0x46790f(0x2c5)+_0x1e6014(0x2bd)+'.')),bindRetryTimer=setTimeout(()=>{const _0x2a663e=_0x1e6014;bindRetryTimer=null,scheduleBindAttempt(_0x131ff9[_0x2a663e(0x174)],0x2585+-0xe*0x179+-0x1*0x10e7);},_0x131ff9[_0x1e6014(0x303)]);};_0xf8adf3['on'](_0x292262(0x26c),_0x26d422);try{const _0x37d6f2=config[_0x292262(0x261)]==='*'||config['webHost']===''?undefined:config[_0x407f8f(0x261)];_0xf8adf3[_0x407f8f(0x151)](_0x390fac,_0x37d6f2,()=>{const _0x3069af=_0x407f8f,_0x4a065c=_0x407f8f;if(_0x1a0942)return;_0x1a0942=!![];const _0x313951=new WebSocketServer({'server':_0xf8adf3});handleWebSocket(_0x313951),currentServer=_0xf8adf3,wsServerRef=_0x313951,actualWebPort=_0x390fac,_0xf8adf3[_0x3069af(0x118)+_0x3069af(0x17f)]('error',_0x26d422),_0xf8adf3['on']('error',_0x2d3091=>{const _0xb87f19=_0x3069af,_0x23cfbf=_0x4a065c;console['warn'](_0xb87f19(0x1c2)+'-bind\x20serv'+_0xb87f19(0x29e)+_0x23cfbf(0x2b1)+_0x2d3091[_0x23cfbf(0x350)]);});const _0x1b242a=_0x37d6f2&&_0x37d6f2!==_0x3069af(0x37e)&&_0x37d6f2!==_0x3069af(0x2c7)?'http://'+_0x37d6f2+':'+actualWebPort+(_0x37d6f2===_0x4a065c(0x1a7)?_0x4a065c(0x22c)+'hable)':''):'http://loc'+_0x4a065c(0x2ee)+actualWebPort;console['log'](_0x4a065c(0x355)+_0x1b242a),actualWebPort!==_0x434b38&&console['log']('\x20\x20\x20(Port\x20'+_0x434b38+(_0x3069af(0x169)+_0x3069af(0x2a5))+actualWebPort+_0x4a065c(0x132)),isExposedWithoutPassword()&&console['log'](_0x4a065c(0x30d)+_0x3069af(0x20b)+_0x3069af(0x105)+'o\x20WEB_PASS'+'WORD:\x20muta'+_0x4a065c(0x1ac)+_0x4a065c(0x16f)+_0x3069af(0x221)+_0x3069af(0x238)+(_0x3069af(0x2be)+_0x4a065c(0x2db)+_0x3069af(0x33f)+_0x4a065c(0x218)+_0x4a065c(0x120)+'ot/.env\x20to'+_0x4a065c(0x12f)+_0x4a065c(0x2df))+('or\x20restric'+_0x3069af(0x183)+_0x3069af(0x226)+'EB_HOST=12'+_0x4a065c(0x295)));});}catch(_0x73ba63){_0x26d422(_0x73ba63);}}export async function closeHttpServerGracefully(_0x33b639){const _0x5cb13d=_0x217a38,_0x76f390=_0x3da471;if(!_0x33b639[_0x5cb13d(0x262)])return;try{const _0x36d28c=_0x33b639;if(typeof _0x36d28c['closeIdleC'+_0x76f390(0x250)]==='function')_0x36d28c[_0x5cb13d(0x200)+_0x5cb13d(0x250)]();if(typeof _0x36d28c[_0x5cb13d(0x207)+_0x76f390(0x2bc)]===_0x76f390(0x270))_0x36d28c['closeAllCo'+'nnections']();}catch{}await new Promise(_0x38bf21=>{_0x33b639['close'](()=>_0x38bf21());});}export async function stopWebServer(){const _0x2e12ee=_0x217a38,_0x551571=_0x217a38;stopRequested=!![];bindRetryTimer&&(clearTimeout(bindRetryTimer),bindRetryTimer=null);if(wsServerRef){try{for(const _0x25c440 of wsServerRef[_0x2e12ee(0x1a2)]){try{_0x25c440[_0x551571(0x147)]();}catch{}}await new Promise(_0x363f95=>wsServerRef[_0x551571(0x197)](()=>_0x363f95()));}catch{}wsServerRef=null;}if(currentServer){try{await closeHttpServerGracefully(currentServer);}catch{}currentServer=null;}}export function getWebPort(){return actualWebPort;}
|