alvin-bot 5.7.0 → 5.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +25 -31
- 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 -174
- 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 -583
- 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 -86
- 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 -1902
- 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,1902 +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 crypto from "crypto";
|
|
16
|
-
import { WebSocketServer, WebSocket } from "ws";
|
|
17
|
-
import { getRegistry } from "../engine.js";
|
|
18
|
-
import { getSession, resetSession, getAllSessions } from "../services/session.js";
|
|
19
|
-
import { getMemoryStats, loadLongTermMemory, loadDailyLog } from "../services/memory.js";
|
|
20
|
-
import { getIndexStats } from "../services/embeddings.js";
|
|
21
|
-
import { getLoadedPlugins } from "../services/plugins.js";
|
|
22
|
-
import { getMCPStatus } from "../services/mcp.js";
|
|
23
|
-
import { listProfiles } from "../services/users.js";
|
|
24
|
-
import { listCustomTools, getCustomTools, executeCustomTool } from "../services/custom-tools.js";
|
|
25
|
-
import { buildSystemPrompt, reloadSoul, getSoulContent } from "../services/personality.js";
|
|
26
|
-
import { config } from "../config.js";
|
|
27
|
-
import { handleSetupAPI } from "./setup-api.js";
|
|
28
|
-
import { handleDoctorAPI } from "./doctor-api.js";
|
|
29
|
-
import { handleOpenAICompat } from "./openai-compat.js";
|
|
30
|
-
import { addCanvasClient } from "./canvas.js";
|
|
31
|
-
import { BOT_ROOT, ENV_FILE, PUBLIC_DIR, MEMORY_DIR, MEMORY_FILE, SOUL_FILE, DATA_DIR, MCP_CONFIG, SKILLS_DIR } from "../paths.js";
|
|
32
|
-
import { writeSecure } from "../services/file-permissions.js";
|
|
33
|
-
import { timingSafeBearerMatch } from "../services/timing-safe-bearer.js";
|
|
34
|
-
import { broadcast } from "../services/broadcast.js";
|
|
35
|
-
import { BOT_VERSION } from "../version.js";
|
|
36
|
-
import { decideNextBindAction } from "./bind-strategy.js";
|
|
37
|
-
const WEB_PORT = parseInt(process.env.WEB_PORT || "3100");
|
|
38
|
-
/** Tuning for the bind loop. Walk the port ladder `MAX_PORT_TRIES` times
|
|
39
|
-
* then fall back to a `BACKGROUND_RETRY_MS` idle loop — the bot keeps
|
|
40
|
-
* running on Telegram either way; see bind-strategy.ts for the pure
|
|
41
|
-
* decision logic. */
|
|
42
|
-
const MAX_PORT_TRIES = 20;
|
|
43
|
-
const BACKGROUND_RETRY_MS = 30_000;
|
|
44
|
-
/** Current live http.Server, if one has successfully bound. */
|
|
45
|
-
let currentServer = null;
|
|
46
|
-
/** Current live WebSocketServer attached to currentServer. */
|
|
47
|
-
let wsServerRef = null;
|
|
48
|
-
/** Background-retry timer handle — set when the bind loop is in its
|
|
49
|
-
* idle wait between cycles, cleared when stopWebServer() cancels. */
|
|
50
|
-
let bindRetryTimer = null;
|
|
51
|
-
/** Flag flipped by stopWebServer(). Every bind-loop callback checks
|
|
52
|
-
* this and exits silently if set, so stop is truly terminal. */
|
|
53
|
-
let stopRequested = false;
|
|
54
|
-
const WEB_PASSWORD = process.env.WEB_PASSWORD || "";
|
|
55
|
-
/**
|
|
56
|
-
* v5.7.0 — Per-boot random token guarding POST /internal/subagent-exit.
|
|
57
|
-
* Generated once at module load; never persisted, never logged. The
|
|
58
|
-
* detached-agent exit hook reads it in-process via getInternalToken().
|
|
59
|
-
* The route is loopback-bound by default; this token additionally
|
|
60
|
-
* defends the opt-in WEB_HOST=0.0.0.0 (LAN-exposed) case. 48 hex chars.
|
|
61
|
-
*/
|
|
62
|
-
const INTERNAL_TOKEN = crypto.randomBytes(24).toString("hex");
|
|
63
|
-
/** In-process accessor for the per-boot internal-route token. */
|
|
64
|
-
export function getInternalToken() {
|
|
65
|
-
return INTERNAL_TOKEN;
|
|
66
|
-
}
|
|
67
|
-
/** The actual port the Web UI is running on (may differ from WEB_PORT if busy). */
|
|
68
|
-
let actualWebPort = WEB_PORT;
|
|
69
|
-
// ── MIME Types ──────────────────────────────────────────
|
|
70
|
-
const MIME = {
|
|
71
|
-
".html": "text/html",
|
|
72
|
-
".css": "text/css",
|
|
73
|
-
".js": "application/javascript",
|
|
74
|
-
".json": "application/json",
|
|
75
|
-
".png": "image/png",
|
|
76
|
-
".jpg": "image/jpeg",
|
|
77
|
-
".svg": "image/svg+xml",
|
|
78
|
-
".ico": "image/x-icon",
|
|
79
|
-
};
|
|
80
|
-
// ── Auth ────────────────────────────────────────────────
|
|
81
|
-
const activeSessions = new Set();
|
|
82
|
-
function generateToken() {
|
|
83
|
-
return Array.from(crypto.getRandomValues(new Uint8Array(32)))
|
|
84
|
-
.map(b => b.toString(16).padStart(2, "0")).join("");
|
|
85
|
-
}
|
|
86
|
-
function checkAuth(req) {
|
|
87
|
-
if (!WEB_PASSWORD)
|
|
88
|
-
return true; // No password = open access
|
|
89
|
-
const cookie = req.headers.cookie || "";
|
|
90
|
-
const token = cookie.match(/alvinbot_token=([a-f0-9]+)/)?.[1];
|
|
91
|
-
return token ? activeSessions.has(token) : false;
|
|
92
|
-
}
|
|
93
|
-
/** Returns true when the server is exposed to non-loopback addresses but
|
|
94
|
-
* WEB_PASSWORD is empty. In that state every mutating / exec endpoint is
|
|
95
|
-
* hard-refused regardless of any session cookie. */
|
|
96
|
-
function isExposedWithoutPassword() {
|
|
97
|
-
if (WEB_PASSWORD)
|
|
98
|
-
return false; // password set → normal auth path
|
|
99
|
-
const h = config.webHost;
|
|
100
|
-
// loopback addresses are safe even without a password
|
|
101
|
-
if (!h || h === "127.0.0.1" || h === "::1" || h === "localhost")
|
|
102
|
-
return false;
|
|
103
|
-
return true; // non-loopback + no password = exposed
|
|
104
|
-
}
|
|
105
|
-
// ── REST API ────────────────────────────────────────────
|
|
106
|
-
async function handleAPI(req, res, urlPath, body) {
|
|
107
|
-
res.setHeader("Content-Type", "application/json");
|
|
108
|
-
// POST /api/login
|
|
109
|
-
if (urlPath === "/api/login" && req.method === "POST") {
|
|
110
|
-
try {
|
|
111
|
-
const { password } = JSON.parse(body);
|
|
112
|
-
// v5.x — refuse login entirely when WEB_PASSWORD is not set.
|
|
113
|
-
// Previously `!WEB_PASSWORD || password === WEB_PASSWORD` minted a
|
|
114
|
-
// session for ANY input (including "") when the env var was absent,
|
|
115
|
-
// effectively making every unauthenticated request a valid session.
|
|
116
|
-
// timingSafeBearerMatch already rejects empty expectedToken → reuse it
|
|
117
|
-
// by wrapping the provided password as a synthetic "Bearer <pw>" header.
|
|
118
|
-
const authOk = timingSafeBearerMatch(`Bearer ${password}`, WEB_PASSWORD);
|
|
119
|
-
if (authOk) {
|
|
120
|
-
const token = generateToken();
|
|
121
|
-
activeSessions.add(token);
|
|
122
|
-
res.setHeader("Set-Cookie", `alvinbot_token=${token}; Path=/; HttpOnly; SameSite=Strict; Max-Age=86400`);
|
|
123
|
-
res.end(JSON.stringify({ ok: true }));
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
res.statusCode = 401;
|
|
127
|
-
res.end(JSON.stringify({ error: "Wrong password" }));
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
res.statusCode = 400;
|
|
132
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
133
|
-
}
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
// POST /api/webhook — external trigger endpoint with bearer auth (no cookie auth needed)
|
|
137
|
-
if (urlPath === "/api/webhook" && req.method === "POST") {
|
|
138
|
-
if (!config.webhookEnabled) {
|
|
139
|
-
res.writeHead(404);
|
|
140
|
-
res.end(JSON.stringify({ error: "Webhooks disabled" }));
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
// v4.12.2 — timing-safe bearer token comparison. Previously used
|
|
144
|
-
// naive !== which leaks comparison position via timing side-channel.
|
|
145
|
-
if (!timingSafeBearerMatch(req.headers.authorization, config.webhookToken ?? "")) {
|
|
146
|
-
res.writeHead(401);
|
|
147
|
-
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
try {
|
|
151
|
-
const payload = JSON.parse(body);
|
|
152
|
-
if (!payload.message) {
|
|
153
|
-
res.writeHead(400);
|
|
154
|
-
res.end(JSON.stringify({ error: "Missing message field" }));
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
const channel = payload.channel || "telegram";
|
|
158
|
-
const chatId = payload.chatId || String(config.allowedUsers[0] || "");
|
|
159
|
-
const { enqueue } = await import("../services/delivery-queue.js");
|
|
160
|
-
const id = enqueue(channel, chatId, `[Webhook: ${payload.event || "unknown"}] ${payload.message}`);
|
|
161
|
-
res.writeHead(200);
|
|
162
|
-
res.end(JSON.stringify({ ok: true, queued: id }));
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
res.writeHead(400);
|
|
166
|
-
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
167
|
-
}
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
// POST /internal/subagent-exit — detached sub-agent exit push (v5.7.0).
|
|
171
|
-
// Always available (NO WEBHOOK_ENABLED dependency, so it works for every
|
|
172
|
-
// install with zero config); loopback-bound by default; per-boot bearer
|
|
173
|
-
// token. Reads the agent's jsonl, classifies, delivers immediately
|
|
174
|
-
// (claim-gated). Idempotent — a repeat POST is a 404 no-op.
|
|
175
|
-
if (urlPath === "/internal/subagent-exit" && req.method === "POST") {
|
|
176
|
-
// The legitimate payload is ~80 bytes ({agentId, exitCode}). Reject
|
|
177
|
-
// anything absurd before the auth compare so an unauthenticated
|
|
178
|
-
// caller (only reachable at all under the opt-in WEB_HOST=0.0.0.0)
|
|
179
|
-
// cannot push large bodies through this always-on route. (A deeper
|
|
180
|
-
// streaming cap on the shared body accumulator — also used by
|
|
181
|
-
// /api/ and /v1/ — is a separate pre-existing hardening item.)
|
|
182
|
-
if (body.length > 8 * 1024) {
|
|
183
|
-
res.statusCode = 413;
|
|
184
|
-
res.end(JSON.stringify({ error: "Payload too large" }));
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
if (!timingSafeBearerMatch(req.headers.authorization, INTERNAL_TOKEN)) {
|
|
188
|
-
res.statusCode = 401;
|
|
189
|
-
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
let agentId;
|
|
193
|
-
try {
|
|
194
|
-
const payload = JSON.parse(body);
|
|
195
|
-
agentId = typeof payload.agentId === "string" ? payload.agentId : "";
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
res.statusCode = 400;
|
|
199
|
-
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if (!agentId) {
|
|
203
|
-
res.statusCode = 400;
|
|
204
|
-
res.end(JSON.stringify({ error: "Missing agentId" }));
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
try {
|
|
208
|
-
const { deliverByAgentId } = await import("../services/async-agent-watcher.js");
|
|
209
|
-
const outcome = await deliverByAgentId(agentId);
|
|
210
|
-
res.statusCode = outcome === "unknown" ? 404 : outcome === "pending" ? 202 : 200;
|
|
211
|
-
res.end(JSON.stringify({ ok: outcome !== "unknown", outcome }));
|
|
212
|
-
}
|
|
213
|
-
catch (err) {
|
|
214
|
-
res.statusCode = 500;
|
|
215
|
-
res.end(JSON.stringify({ error: "delivery failed" }));
|
|
216
|
-
console.error("[internal/subagent-exit] delivery error:", err);
|
|
217
|
-
}
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
// Auth check for all other API routes
|
|
221
|
-
if (!checkAuth(req)) {
|
|
222
|
-
res.statusCode = 401;
|
|
223
|
-
res.end(JSON.stringify({ error: "Not authenticated" }));
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
// Hard gate: when the web server is reachable from non-loopback addresses
|
|
227
|
-
// and WEB_PASSWORD is empty, mutating/exec endpoints are refused outright.
|
|
228
|
-
// Local dev (127.0.0.1 + no password) is intentionally unaffected.
|
|
229
|
-
// Endpoints that write files, execute commands, or modify configuration
|
|
230
|
-
// are in scope; read-only status/config endpoints are not gated.
|
|
231
|
-
const EXPOSED_DANGEROUS_ROUTES = new Set([
|
|
232
|
-
"/api/terminal",
|
|
233
|
-
"/api/skills/create",
|
|
234
|
-
"/api/skills/update",
|
|
235
|
-
"/api/skills/delete",
|
|
236
|
-
"/api/files/save",
|
|
237
|
-
"/api/files/delete",
|
|
238
|
-
"/api/env/set",
|
|
239
|
-
"/api/setup-wizard",
|
|
240
|
-
"/api/soul/save",
|
|
241
|
-
"/api/memory/save",
|
|
242
|
-
"/api/memory/delete",
|
|
243
|
-
"/api/session/reset",
|
|
244
|
-
// cron — /api/cron/add was wrong; real route is /api/cron/create
|
|
245
|
-
"/api/cron/create",
|
|
246
|
-
"/api/cron/update",
|
|
247
|
-
"/api/cron/delete",
|
|
248
|
-
"/api/cron/toggle",
|
|
249
|
-
"/api/cron/run",
|
|
250
|
-
"/api/plugin/install",
|
|
251
|
-
"/api/plugin/uninstall",
|
|
252
|
-
// shell / process exec
|
|
253
|
-
"/api/tools/execute",
|
|
254
|
-
"/api/sudo/exec",
|
|
255
|
-
"/api/sudo/setup",
|
|
256
|
-
"/api/sudo/admin-dialog",
|
|
257
|
-
"/api/sudo/revoke",
|
|
258
|
-
// key / env writes
|
|
259
|
-
"/api/providers/set-key",
|
|
260
|
-
"/api/providers/set-primary",
|
|
261
|
-
"/api/providers/set-fallbacks",
|
|
262
|
-
"/api/providers/add-custom",
|
|
263
|
-
"/api/providers/remove-custom",
|
|
264
|
-
// platform config writes (writes ENV vars / runs npm install)
|
|
265
|
-
"/api/platforms/configure",
|
|
266
|
-
"/api/platforms/install-deps",
|
|
267
|
-
// provider / fallback order writes
|
|
268
|
-
"/api/fallback",
|
|
269
|
-
"/api/fallback/move",
|
|
270
|
-
// MCP config writes
|
|
271
|
-
"/api/mcp/add",
|
|
272
|
-
"/api/mcp/remove",
|
|
273
|
-
// process restart (DoS vector)
|
|
274
|
-
"/api/restart",
|
|
275
|
-
// model switching (affects all users)
|
|
276
|
-
"/api/models/switch",
|
|
277
|
-
// WhatsApp auth / config writes
|
|
278
|
-
"/api/whatsapp/disconnect",
|
|
279
|
-
"/api/whatsapp/group-rules",
|
|
280
|
-
]);
|
|
281
|
-
if (isExposedWithoutPassword() && EXPOSED_DANGEROUS_ROUTES.has(urlPath)) {
|
|
282
|
-
res.statusCode = 403;
|
|
283
|
-
res.end(JSON.stringify({ error: "refused: web exposed without WEB_PASSWORD" }));
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
// ── Setup APIs (platforms + models) ─────────────────
|
|
287
|
-
const handled = await handleSetupAPI(req, res, urlPath, body);
|
|
288
|
-
if (handled)
|
|
289
|
-
return;
|
|
290
|
-
// ── Doctor & Backup APIs ──────────────────────────
|
|
291
|
-
const doctorHandled = await handleDoctorAPI(req, res, urlPath, body);
|
|
292
|
-
if (doctorHandled)
|
|
293
|
-
return;
|
|
294
|
-
// GET /api/setup-check — is the bot fully configured?
|
|
295
|
-
if (urlPath === "/api/setup-check") {
|
|
296
|
-
const envPath = ENV_FILE;
|
|
297
|
-
let env = {};
|
|
298
|
-
try {
|
|
299
|
-
const lines = fs.readFileSync(envPath, "utf-8").split("\n");
|
|
300
|
-
for (const line of lines) {
|
|
301
|
-
if (line.startsWith("#") || !line.includes("="))
|
|
302
|
-
continue;
|
|
303
|
-
const idx = line.indexOf("=");
|
|
304
|
-
env[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
catch { }
|
|
308
|
-
const hasBotToken = !!(env.BOT_TOKEN || process.env.BOT_TOKEN);
|
|
309
|
-
const hasAllowedUsers = !!(env.ALLOWED_USERS || process.env.ALLOWED_USERS);
|
|
310
|
-
const hasPrimaryProvider = !!(env.PRIMARY_PROVIDER || process.env.PRIMARY_PROVIDER);
|
|
311
|
-
// Check which providers have keys
|
|
312
|
-
const providerKeys = {
|
|
313
|
-
groq: !!(env.GROQ_API_KEY || process.env.GROQ_API_KEY),
|
|
314
|
-
openai: !!(env.OPENAI_API_KEY || process.env.OPENAI_API_KEY),
|
|
315
|
-
google: !!(env.GOOGLE_API_KEY || process.env.GOOGLE_API_KEY),
|
|
316
|
-
nvidia: !!(env.NVIDIA_API_KEY || process.env.NVIDIA_API_KEY),
|
|
317
|
-
anthropic: !!(env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY),
|
|
318
|
-
openrouter: !!(env.OPENROUTER_API_KEY || process.env.OPENROUTER_API_KEY),
|
|
319
|
-
};
|
|
320
|
-
const hasAnyProvider = hasPrimaryProvider || Object.values(providerKeys).some(Boolean);
|
|
321
|
-
// Check Claude CLI
|
|
322
|
-
let claudeCliInstalled = false;
|
|
323
|
-
try {
|
|
324
|
-
const { execSync } = await import("child_process");
|
|
325
|
-
execSync("claude --version", { timeout: 5000, stdio: "pipe" });
|
|
326
|
-
claudeCliInstalled = true;
|
|
327
|
-
}
|
|
328
|
-
catch { }
|
|
329
|
-
const isComplete = hasBotToken && hasAllowedUsers && hasAnyProvider;
|
|
330
|
-
res.end(JSON.stringify({
|
|
331
|
-
isComplete,
|
|
332
|
-
steps: {
|
|
333
|
-
telegram: { done: hasBotToken && hasAllowedUsers, botToken: hasBotToken, allowedUsers: hasAllowedUsers },
|
|
334
|
-
provider: { done: hasAnyProvider, primary: env.PRIMARY_PROVIDER || process.env.PRIMARY_PROVIDER || "", keys: providerKeys, claudeCli: claudeCliInstalled },
|
|
335
|
-
},
|
|
336
|
-
}));
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
// POST /api/setup-wizard — save all setup data at once (first-run wizard)
|
|
340
|
-
if (urlPath === "/api/setup-wizard" && req.method === "POST") {
|
|
341
|
-
try {
|
|
342
|
-
const data = JSON.parse(body);
|
|
343
|
-
const envPath = ENV_FILE;
|
|
344
|
-
let content = fs.existsSync(envPath) ? fs.readFileSync(envPath, "utf-8") : "";
|
|
345
|
-
const setEnv = (key, value) => {
|
|
346
|
-
// M6 (centralized): reject values containing newline characters
|
|
347
|
-
if (/[\n\r]/.test(value))
|
|
348
|
-
throw new Error("env value must not contain newline characters");
|
|
349
|
-
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
350
|
-
if (regex.test(content)) {
|
|
351
|
-
content = content.replace(regex, `${key}=${value}`);
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
content = content.trimEnd() + `\n${key}=${value}\n`;
|
|
355
|
-
}
|
|
356
|
-
process.env[key] = value;
|
|
357
|
-
};
|
|
358
|
-
// Step 1: Telegram
|
|
359
|
-
if (data.botToken)
|
|
360
|
-
setEnv("BOT_TOKEN", data.botToken);
|
|
361
|
-
if (data.allowedUsers)
|
|
362
|
-
setEnv("ALLOWED_USERS", data.allowedUsers);
|
|
363
|
-
// Step 2: Provider
|
|
364
|
-
if (data.primaryProvider)
|
|
365
|
-
setEnv("PRIMARY_PROVIDER", data.primaryProvider);
|
|
366
|
-
if (data.apiKey && data.apiKeyEnv)
|
|
367
|
-
setEnv(data.apiKeyEnv, data.apiKey);
|
|
368
|
-
// Step 3: Optional
|
|
369
|
-
if (data.webPassword)
|
|
370
|
-
setEnv("WEB_PASSWORD", data.webPassword);
|
|
371
|
-
fs.writeFileSync(envPath, content);
|
|
372
|
-
res.end(JSON.stringify({ ok: true, note: "Setup complete! Restart needed." }));
|
|
373
|
-
}
|
|
374
|
-
catch (e) {
|
|
375
|
-
res.statusCode = 400;
|
|
376
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
377
|
-
}
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
// POST /api/validate-bot-token — validate a Telegram bot token
|
|
381
|
-
if (urlPath === "/api/validate-bot-token" && req.method === "POST") {
|
|
382
|
-
try {
|
|
383
|
-
const { token } = JSON.parse(body);
|
|
384
|
-
if (!token || !token.includes(":")) {
|
|
385
|
-
res.end(JSON.stringify({ ok: false, error: "Invalid token format" }));
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
const tgRes = await fetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
389
|
-
const tgData = await tgRes.json();
|
|
390
|
-
if (tgData.ok) {
|
|
391
|
-
res.end(JSON.stringify({ ok: true, bot: { username: tgData.result.username, firstName: tgData.result.first_name, id: tgData.result.id } }));
|
|
392
|
-
}
|
|
393
|
-
else {
|
|
394
|
-
res.end(JSON.stringify({ ok: false, error: tgData.description || "Invalid token" }));
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
catch (e) {
|
|
398
|
-
res.end(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
399
|
-
}
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
// GET /api/status
|
|
403
|
-
if (urlPath === "/api/status") {
|
|
404
|
-
let modelInfo = { name: "Not configured", model: "none", status: "unconfigured" };
|
|
405
|
-
try {
|
|
406
|
-
const registry = getRegistry();
|
|
407
|
-
const active = registry.getActive().getInfo();
|
|
408
|
-
modelInfo = { name: active.name, model: active.model, status: active.status };
|
|
409
|
-
}
|
|
410
|
-
catch { /* engine not initialized — no provider configured */ }
|
|
411
|
-
const memory = getMemoryStats();
|
|
412
|
-
const index = getIndexStats();
|
|
413
|
-
const plugins = getLoadedPlugins();
|
|
414
|
-
const mcp = getMCPStatus();
|
|
415
|
-
const users = listProfiles();
|
|
416
|
-
const tools = listCustomTools();
|
|
417
|
-
// Aggregate token usage across all sessions
|
|
418
|
-
const { getAllSessions } = await import("../services/session.js");
|
|
419
|
-
const allSessions = getAllSessions();
|
|
420
|
-
let totalInputTokens = 0, totalOutputTokens = 0, totalCost = 0;
|
|
421
|
-
for (const s of allSessions.values()) {
|
|
422
|
-
totalInputTokens += s.totalInputTokens || 0;
|
|
423
|
-
totalOutputTokens += s.totalOutputTokens || 0;
|
|
424
|
-
totalCost += s.totalCost || 0;
|
|
425
|
-
}
|
|
426
|
-
const { config: appConfig } = await import("../config.js");
|
|
427
|
-
res.end(JSON.stringify({
|
|
428
|
-
bot: { version: BOT_VERSION, uptime: process.uptime() },
|
|
429
|
-
model: modelInfo,
|
|
430
|
-
memory: { ...memory, vectors: index.entries, indexSize: index.sizeBytes },
|
|
431
|
-
plugins: plugins.length,
|
|
432
|
-
mcp: mcp.length,
|
|
433
|
-
users: users.length,
|
|
434
|
-
tools: tools.length,
|
|
435
|
-
tokens: {
|
|
436
|
-
totalInput: totalInputTokens,
|
|
437
|
-
totalOutput: totalOutputTokens,
|
|
438
|
-
total: totalInputTokens + totalOutputTokens,
|
|
439
|
-
totalCost,
|
|
440
|
-
},
|
|
441
|
-
setup: {
|
|
442
|
-
telegram: !!appConfig.botToken,
|
|
443
|
-
provider: modelInfo.status !== "unconfigured",
|
|
444
|
-
},
|
|
445
|
-
}));
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
// GET /api/models
|
|
449
|
-
if (urlPath === "/api/models") {
|
|
450
|
-
const registry = getRegistry();
|
|
451
|
-
registry.listAll().then(models => {
|
|
452
|
-
res.end(JSON.stringify({ models, active: registry.getActiveKey() }));
|
|
453
|
-
});
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
// POST /api/models/switch
|
|
457
|
-
if (urlPath === "/api/models/switch" && req.method === "POST") {
|
|
458
|
-
try {
|
|
459
|
-
const { key } = JSON.parse(body);
|
|
460
|
-
const registry = getRegistry();
|
|
461
|
-
const ok = registry.switchTo(key);
|
|
462
|
-
res.end(JSON.stringify({ ok, active: registry.getActiveKey() }));
|
|
463
|
-
}
|
|
464
|
-
catch {
|
|
465
|
-
res.statusCode = 400;
|
|
466
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
467
|
-
}
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
// GET /api/fallback — Get fallback order + health
|
|
471
|
-
if (urlPath === "/api/fallback" && req.method === "GET") {
|
|
472
|
-
try {
|
|
473
|
-
const { getFallbackOrder } = await import("../services/fallback-order.js");
|
|
474
|
-
const { getHealthStatus, isFailedOver } = await import("../services/heartbeat.js");
|
|
475
|
-
const registry = getRegistry();
|
|
476
|
-
const providers = await registry.listAll();
|
|
477
|
-
res.end(JSON.stringify({
|
|
478
|
-
order: getFallbackOrder(),
|
|
479
|
-
health: getHealthStatus(),
|
|
480
|
-
failedOver: isFailedOver(),
|
|
481
|
-
activeProvider: registry.getActiveKey(),
|
|
482
|
-
availableProviders: providers.map(p => ({ key: p.key, name: p.name, status: p.status })),
|
|
483
|
-
}));
|
|
484
|
-
}
|
|
485
|
-
catch (err) {
|
|
486
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
487
|
-
}
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
// POST /api/fallback — Set fallback order
|
|
491
|
-
if (urlPath === "/api/fallback" && req.method === "POST") {
|
|
492
|
-
try {
|
|
493
|
-
const { primary, fallbacks } = JSON.parse(body);
|
|
494
|
-
const { setFallbackOrder } = await import("../services/fallback-order.js");
|
|
495
|
-
const result = setFallbackOrder(primary, fallbacks, "webui");
|
|
496
|
-
res.end(JSON.stringify({ ok: true, order: result }));
|
|
497
|
-
}
|
|
498
|
-
catch (err) {
|
|
499
|
-
res.statusCode = 400;
|
|
500
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
501
|
-
}
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
// POST /api/fallback/move — Move provider up/down
|
|
505
|
-
if (urlPath === "/api/fallback/move" && req.method === "POST") {
|
|
506
|
-
try {
|
|
507
|
-
const { key, direction } = JSON.parse(body);
|
|
508
|
-
const fb = await import("../services/fallback-order.js");
|
|
509
|
-
const result = direction === "up" ? fb.moveUp(key, "webui") : fb.moveDown(key, "webui");
|
|
510
|
-
res.end(JSON.stringify({ ok: true, order: result }));
|
|
511
|
-
}
|
|
512
|
-
catch (err) {
|
|
513
|
-
res.statusCode = 400;
|
|
514
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
515
|
-
}
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
// GET /api/heartbeat — Health status
|
|
519
|
-
if (urlPath === "/api/heartbeat") {
|
|
520
|
-
try {
|
|
521
|
-
const { getHealthStatus, isFailedOver } = await import("../services/heartbeat.js");
|
|
522
|
-
res.end(JSON.stringify({
|
|
523
|
-
health: getHealthStatus(),
|
|
524
|
-
failedOver: isFailedOver(),
|
|
525
|
-
}));
|
|
526
|
-
}
|
|
527
|
-
catch (err) {
|
|
528
|
-
res.end(JSON.stringify({ health: [], failedOver: false }));
|
|
529
|
-
}
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
// GET /api/memory
|
|
533
|
-
if (urlPath === "/api/memory") {
|
|
534
|
-
const ltm = loadLongTermMemory();
|
|
535
|
-
const todayLog = loadDailyLog();
|
|
536
|
-
const stats = getMemoryStats();
|
|
537
|
-
const index = getIndexStats();
|
|
538
|
-
// List daily log files
|
|
539
|
-
let dailyFiles = [];
|
|
540
|
-
try {
|
|
541
|
-
dailyFiles = fs.readdirSync(MEMORY_DIR)
|
|
542
|
-
.filter(f => f.endsWith(".md") && !f.startsWith("."))
|
|
543
|
-
.sort()
|
|
544
|
-
.reverse();
|
|
545
|
-
}
|
|
546
|
-
catch { /* empty */ }
|
|
547
|
-
res.end(JSON.stringify({
|
|
548
|
-
longTermMemory: ltm,
|
|
549
|
-
todayLog,
|
|
550
|
-
dailyFiles,
|
|
551
|
-
stats,
|
|
552
|
-
index: { entries: index.entries, files: index.files, sizeBytes: index.sizeBytes },
|
|
553
|
-
}));
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
// GET /api/memory/:file
|
|
557
|
-
if (urlPath.startsWith("/api/memory/")) {
|
|
558
|
-
const file = urlPath.slice(12);
|
|
559
|
-
if (file.includes("..") || !file.endsWith(".md")) {
|
|
560
|
-
res.statusCode = 400;
|
|
561
|
-
res.end(JSON.stringify({ error: "Invalid file" }));
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
try {
|
|
565
|
-
const content = fs.readFileSync(resolve(MEMORY_DIR, file), "utf-8");
|
|
566
|
-
res.end(JSON.stringify({ file, content }));
|
|
567
|
-
}
|
|
568
|
-
catch {
|
|
569
|
-
res.statusCode = 404;
|
|
570
|
-
res.end(JSON.stringify({ error: "File not found" }));
|
|
571
|
-
}
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
// POST /api/memory/save
|
|
575
|
-
if (urlPath === "/api/memory/save" && req.method === "POST") {
|
|
576
|
-
try {
|
|
577
|
-
const { file, content } = JSON.parse(body);
|
|
578
|
-
if (file === "MEMORY.md") {
|
|
579
|
-
fs.writeFileSync(MEMORY_FILE, content);
|
|
580
|
-
}
|
|
581
|
-
else if (file.endsWith(".md") && !file.includes("..")) {
|
|
582
|
-
fs.writeFileSync(resolve(MEMORY_DIR, file), content);
|
|
583
|
-
}
|
|
584
|
-
else {
|
|
585
|
-
res.statusCode = 400;
|
|
586
|
-
res.end(JSON.stringify({ error: "Invalid file" }));
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
res.end(JSON.stringify({ ok: true }));
|
|
590
|
-
}
|
|
591
|
-
catch {
|
|
592
|
-
res.statusCode = 400;
|
|
593
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
594
|
-
}
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
// GET /api/plugins
|
|
598
|
-
if (urlPath === "/api/plugins") {
|
|
599
|
-
res.end(JSON.stringify({ plugins: getLoadedPlugins() }));
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
// v4.12.0 — Workspace overview: registry + per-workspace cost breakdown
|
|
603
|
-
if (urlPath === "/api/workspaces") {
|
|
604
|
-
try {
|
|
605
|
-
const { listWorkspaces, getDefaultWorkspace } = await import("../services/workspaces.js");
|
|
606
|
-
const { getCostByWorkspace } = await import("../services/session.js");
|
|
607
|
-
const costs = getCostByWorkspace();
|
|
608
|
-
const registered = listWorkspaces();
|
|
609
|
-
const all = [getDefaultWorkspace(), ...registered];
|
|
610
|
-
const payload = all.map((ws) => ({
|
|
611
|
-
name: ws.name,
|
|
612
|
-
purpose: ws.purpose,
|
|
613
|
-
emoji: ws.emoji ?? null,
|
|
614
|
-
color: ws.color ?? null,
|
|
615
|
-
cwd: ws.cwd,
|
|
616
|
-
channels: ws.channels,
|
|
617
|
-
stats: costs[ws.name] ?? { totalCost: 0, sessionCount: 0, messageCount: 0, toolUseCount: 0 },
|
|
618
|
-
}));
|
|
619
|
-
res.end(JSON.stringify({ workspaces: payload }));
|
|
620
|
-
}
|
|
621
|
-
catch (err) {
|
|
622
|
-
res.statusCode = 500;
|
|
623
|
-
res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
|
|
624
|
-
}
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
// GET /api/users — Enhanced with session data
|
|
628
|
-
if (urlPath === "/api/users" && req.method === "GET") {
|
|
629
|
-
const { getAllSessions } = await import("../services/session.js");
|
|
630
|
-
const profiles = listProfiles();
|
|
631
|
-
const sessions = getAllSessions();
|
|
632
|
-
const sessionMap = new Map(Array.from(sessions.entries()).map(([k, s]) => [Number(k), s]));
|
|
633
|
-
const enriched = profiles.map(p => {
|
|
634
|
-
const session = sessionMap.get(p.userId);
|
|
635
|
-
return {
|
|
636
|
-
...p,
|
|
637
|
-
session: session ? {
|
|
638
|
-
isProcessing: session.isProcessing,
|
|
639
|
-
totalCost: session.totalCost,
|
|
640
|
-
historyLength: session.history.length,
|
|
641
|
-
effort: session.effort,
|
|
642
|
-
voiceReply: session.voiceReply,
|
|
643
|
-
startedAt: session.startedAt,
|
|
644
|
-
messageCount: session.messageCount,
|
|
645
|
-
toolUseCount: session.toolUseCount,
|
|
646
|
-
workingDir: session.workingDir,
|
|
647
|
-
hasActiveQuery: !!session.abortController,
|
|
648
|
-
queuedMessages: session.messageQueue.length,
|
|
649
|
-
} : null,
|
|
650
|
-
};
|
|
651
|
-
});
|
|
652
|
-
res.end(JSON.stringify({ users: enriched }));
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
// DELETE /api/users/:id — Kill session + delete user data
|
|
656
|
-
if (urlPath.startsWith("/api/users/") && req.method === "DELETE") {
|
|
657
|
-
// Pattern route — gate here since EXPOSED_DANGEROUS_ROUTES can't express :id
|
|
658
|
-
if (isExposedWithoutPassword()) {
|
|
659
|
-
res.statusCode = 403;
|
|
660
|
-
res.end(JSON.stringify({ error: "refused: web exposed without WEB_PASSWORD" }));
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
const userId = parseInt(urlPath.split("/").pop() || "0");
|
|
664
|
-
if (!userId) {
|
|
665
|
-
res.statusCode = 400;
|
|
666
|
-
res.end(JSON.stringify({ error: "Invalid user ID" }));
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
const { deleteUser } = await import("../services/users.js");
|
|
670
|
-
const result = deleteUser(userId);
|
|
671
|
-
res.end(JSON.stringify({ ok: true, ...result }));
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
// GET /api/tools
|
|
675
|
-
if (urlPath === "/api/tools") {
|
|
676
|
-
const tools = getCustomTools();
|
|
677
|
-
res.end(JSON.stringify({ tools }));
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
// POST /api/tools/execute — run a tool by name
|
|
681
|
-
if (urlPath === "/api/tools/execute" && req.method === "POST") {
|
|
682
|
-
try {
|
|
683
|
-
const { name, params } = JSON.parse(body);
|
|
684
|
-
if (!name) {
|
|
685
|
-
res.statusCode = 400;
|
|
686
|
-
res.end(JSON.stringify({ error: "No tool name" }));
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
const output = await executeCustomTool(name, params || {});
|
|
690
|
-
res.end(JSON.stringify({ ok: true, output }));
|
|
691
|
-
}
|
|
692
|
-
catch (err) {
|
|
693
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
694
|
-
res.end(JSON.stringify({ error }));
|
|
695
|
-
}
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
// ── MCP Management ─────────────────────────────────────
|
|
699
|
-
// GET /api/mcp — list MCP servers + tools
|
|
700
|
-
if (urlPath === "/api/mcp") {
|
|
701
|
-
const { getMCPStatus, getMCPTools, hasMCPConfig } = await import("../services/mcp.js");
|
|
702
|
-
const servers = getMCPStatus();
|
|
703
|
-
const tools = getMCPTools();
|
|
704
|
-
// Read raw config for editing
|
|
705
|
-
const configPath = MCP_CONFIG;
|
|
706
|
-
let rawConfig = { servers: {} };
|
|
707
|
-
try {
|
|
708
|
-
rawConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
709
|
-
}
|
|
710
|
-
catch { }
|
|
711
|
-
res.end(JSON.stringify({ servers, tools, config: rawConfig, hasConfig: hasMCPConfig() }));
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
// POST /api/mcp/add — add a new MCP server
|
|
715
|
-
if (urlPath === "/api/mcp/add" && req.method === "POST") {
|
|
716
|
-
try {
|
|
717
|
-
const { name, command, args, url: serverUrl, env, headers } = JSON.parse(body);
|
|
718
|
-
if (!name) {
|
|
719
|
-
res.statusCode = 400;
|
|
720
|
-
res.end(JSON.stringify({ error: "Name required" }));
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
const configPath = MCP_CONFIG;
|
|
724
|
-
let config = { servers: {} };
|
|
725
|
-
try {
|
|
726
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
727
|
-
}
|
|
728
|
-
catch { }
|
|
729
|
-
const entry = {};
|
|
730
|
-
if (command) {
|
|
731
|
-
entry.command = command;
|
|
732
|
-
entry.args = args || [];
|
|
733
|
-
if (env)
|
|
734
|
-
entry.env = env;
|
|
735
|
-
}
|
|
736
|
-
else if (serverUrl) {
|
|
737
|
-
entry.url = serverUrl;
|
|
738
|
-
if (headers)
|
|
739
|
-
entry.headers = headers;
|
|
740
|
-
}
|
|
741
|
-
else {
|
|
742
|
-
res.statusCode = 400;
|
|
743
|
-
res.end(JSON.stringify({ error: "command or url required" }));
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
config.servers[name] = entry;
|
|
747
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
748
|
-
res.end(JSON.stringify({ ok: true, note: "Restart needed to connect." }));
|
|
749
|
-
}
|
|
750
|
-
catch (e) {
|
|
751
|
-
res.statusCode = 400;
|
|
752
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
753
|
-
}
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
// POST /api/mcp/remove — remove an MCP server
|
|
757
|
-
if (urlPath === "/api/mcp/remove" && req.method === "POST") {
|
|
758
|
-
try {
|
|
759
|
-
const { name } = JSON.parse(body);
|
|
760
|
-
const configPath = MCP_CONFIG;
|
|
761
|
-
let config = { servers: {} };
|
|
762
|
-
try {
|
|
763
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
764
|
-
}
|
|
765
|
-
catch { }
|
|
766
|
-
delete config.servers[name];
|
|
767
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
768
|
-
res.end(JSON.stringify({ ok: true }));
|
|
769
|
-
}
|
|
770
|
-
catch (e) {
|
|
771
|
-
res.statusCode = 400;
|
|
772
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
773
|
-
}
|
|
774
|
-
return;
|
|
775
|
-
}
|
|
776
|
-
// GET /api/mcp/discover — auto-discover MCP servers on the system
|
|
777
|
-
if (urlPath === "/api/mcp/discover") {
|
|
778
|
-
const discovered = [];
|
|
779
|
-
const { execSync } = await import("child_process");
|
|
780
|
-
// Check for common MCP server npm packages
|
|
781
|
-
const knownServers = [
|
|
782
|
-
{ pkg: "@modelcontextprotocol/server-filesystem", name: "filesystem", args: ["/tmp"] },
|
|
783
|
-
{ pkg: "@modelcontextprotocol/server-brave-search", name: "brave-search", args: [] },
|
|
784
|
-
{ pkg: "@modelcontextprotocol/server-github", name: "github", args: [] },
|
|
785
|
-
{ pkg: "@modelcontextprotocol/server-postgres", name: "postgres", args: [] },
|
|
786
|
-
{ pkg: "@modelcontextprotocol/server-sqlite", name: "sqlite", args: [] },
|
|
787
|
-
{ pkg: "@modelcontextprotocol/server-slack", name: "slack", args: [] },
|
|
788
|
-
{ pkg: "@modelcontextprotocol/server-memory", name: "memory", args: [] },
|
|
789
|
-
{ pkg: "@modelcontextprotocol/server-puppeteer", name: "puppeteer", args: [] },
|
|
790
|
-
{ pkg: "@modelcontextprotocol/server-fetch", name: "web-fetch", args: [] },
|
|
791
|
-
{ pkg: "@anthropic/mcp-server-sequential-thinking", name: "sequential-thinking", args: [] },
|
|
792
|
-
];
|
|
793
|
-
for (const s of knownServers) {
|
|
794
|
-
try {
|
|
795
|
-
execSync(`npx --yes ${s.pkg} --help`, { timeout: 5000, stdio: "pipe", env: { ...process.env, PATH: process.env.PATH + ":/opt/homebrew/bin:/usr/local/bin" } });
|
|
796
|
-
discovered.push({ name: s.name, command: "npx", args: ["-y", s.pkg, ...s.args], source: "npm" });
|
|
797
|
-
}
|
|
798
|
-
catch {
|
|
799
|
-
// Not installed — try checking if globally available
|
|
800
|
-
try {
|
|
801
|
-
execSync(`npm list -g ${s.pkg} --depth=0`, { timeout: 5000, stdio: "pipe" });
|
|
802
|
-
discovered.push({ name: s.name, command: "npx", args: ["-y", s.pkg, ...s.args], source: "npm-global" });
|
|
803
|
-
}
|
|
804
|
-
catch { /* not installed */ }
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
// Check for Claude Desktop MCP config
|
|
808
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
809
|
-
const claudeConfigPaths = [
|
|
810
|
-
resolve(homeDir, ".config/claude/claude_desktop_config.json"),
|
|
811
|
-
resolve(homeDir, "Library/Application Support/Claude/claude_desktop_config.json"),
|
|
812
|
-
resolve(homeDir, "AppData/Roaming/Claude/claude_desktop_config.json"),
|
|
813
|
-
];
|
|
814
|
-
for (const cfgPath of claudeConfigPaths) {
|
|
815
|
-
try {
|
|
816
|
-
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
817
|
-
if (cfg.mcpServers) {
|
|
818
|
-
for (const [name, srv] of Object.entries(cfg.mcpServers)) {
|
|
819
|
-
if (srv.command) {
|
|
820
|
-
discovered.push({ name: `claude-${name}`, command: srv.command, args: srv.args || [], source: "claude-desktop" });
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
catch { /* not found */ }
|
|
826
|
-
}
|
|
827
|
-
res.end(JSON.stringify({ discovered }));
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
// ── Skills Management ─────────────────────────────────
|
|
831
|
-
// GET /api/skills — already in setup-api.ts, but add full CRUD here
|
|
832
|
-
// GET /api/skills/detail/:id — get full skill content
|
|
833
|
-
if (urlPath?.match(/^\/api\/skills\/detail\//) && req.method === "GET") {
|
|
834
|
-
const skillId = urlPath.split("/").pop();
|
|
835
|
-
const { getSkills } = await import("../services/skills.js");
|
|
836
|
-
const skill = getSkills().find(s => s.id === skillId);
|
|
837
|
-
if (skill) {
|
|
838
|
-
res.end(JSON.stringify({ ok: true, skill }));
|
|
839
|
-
}
|
|
840
|
-
else {
|
|
841
|
-
res.statusCode = 404;
|
|
842
|
-
res.end(JSON.stringify({ error: "Skill not found" }));
|
|
843
|
-
}
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
// POST /api/skills/create — create a new skill
|
|
847
|
-
if (urlPath === "/api/skills/create" && req.method === "POST") {
|
|
848
|
-
try {
|
|
849
|
-
const { id, name, description, triggers, category, content, priority } = JSON.parse(body);
|
|
850
|
-
if (!id || !name) {
|
|
851
|
-
res.statusCode = 400;
|
|
852
|
-
res.end(JSON.stringify({ error: "id and name required" }));
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
// Security: reject id values that could escape SKILLS_DIR.
|
|
856
|
-
// Mirror the resolve+startsWith containment pattern used in /api/files/save (server.ts ~:921).
|
|
857
|
-
// Also guard against ids with path separators or absolute paths before
|
|
858
|
-
// resolve() ever runs, so the raw string never reaches the filesystem.
|
|
859
|
-
if (typeof id !== "string" ||
|
|
860
|
-
id.includes("..") ||
|
|
861
|
-
id.includes("/") ||
|
|
862
|
-
id.includes("\\") ||
|
|
863
|
-
path.isAbsolute(id)) {
|
|
864
|
-
res.statusCode = 400;
|
|
865
|
-
res.end(JSON.stringify({ error: "Invalid skill id" }));
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
const skillsDir = SKILLS_DIR;
|
|
869
|
-
const skillDir = resolve(skillsDir, id);
|
|
870
|
-
// Belt-and-suspenders: resolved path must still be inside SKILLS_DIR
|
|
871
|
-
if (!skillDir.startsWith(skillsDir)) {
|
|
872
|
-
res.statusCode = 400;
|
|
873
|
-
res.end(JSON.stringify({ error: "Invalid skill id" }));
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
if (!fs.existsSync(skillDir))
|
|
877
|
-
fs.mkdirSync(skillDir, { recursive: true });
|
|
878
|
-
const frontmatter = [
|
|
879
|
-
"---",
|
|
880
|
-
`name: ${name}`,
|
|
881
|
-
description ? `description: ${description}` : "",
|
|
882
|
-
triggers ? `triggers: ${Array.isArray(triggers) ? triggers.join(", ") : triggers}` : "",
|
|
883
|
-
`priority: ${priority || 3}`,
|
|
884
|
-
`category: ${category || "custom"}`,
|
|
885
|
-
"---",
|
|
886
|
-
].filter(Boolean).join("\n");
|
|
887
|
-
fs.writeFileSync(resolve(skillDir, "SKILL.md"), `${frontmatter}\n\n${content || ""}`);
|
|
888
|
-
// Force reload
|
|
889
|
-
const { loadSkills } = await import("../services/skills.js");
|
|
890
|
-
loadSkills();
|
|
891
|
-
res.end(JSON.stringify({ ok: true }));
|
|
892
|
-
}
|
|
893
|
-
catch (e) {
|
|
894
|
-
res.statusCode = 400;
|
|
895
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
896
|
-
}
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
// POST /api/skills/update — update an existing skill
|
|
900
|
-
if (urlPath === "/api/skills/update" && req.method === "POST") {
|
|
901
|
-
try {
|
|
902
|
-
const { id, content } = JSON.parse(body);
|
|
903
|
-
// Security: same id containment as /api/skills/create
|
|
904
|
-
if (typeof id !== "string" ||
|
|
905
|
-
id.includes("..") ||
|
|
906
|
-
id.includes("/") ||
|
|
907
|
-
id.includes("\\") ||
|
|
908
|
-
path.isAbsolute(id)) {
|
|
909
|
-
res.statusCode = 400;
|
|
910
|
-
res.end(JSON.stringify({ error: "Invalid skill id" }));
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
|
-
// Belt-and-suspenders: resolved path must still be inside SKILLS_DIR
|
|
914
|
-
if (!resolve(SKILLS_DIR, id).startsWith(SKILLS_DIR)) {
|
|
915
|
-
res.statusCode = 400;
|
|
916
|
-
res.end(JSON.stringify({ error: "Invalid skill id" }));
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
const skillPath = resolve(SKILLS_DIR, id, "SKILL.md");
|
|
920
|
-
if (!fs.existsSync(skillPath)) {
|
|
921
|
-
// Try flat file
|
|
922
|
-
const flatPath = resolve(SKILLS_DIR, id + ".md");
|
|
923
|
-
if (fs.existsSync(flatPath)) {
|
|
924
|
-
fs.writeFileSync(flatPath, content);
|
|
925
|
-
}
|
|
926
|
-
else {
|
|
927
|
-
res.statusCode = 404;
|
|
928
|
-
res.end(JSON.stringify({ error: "Skill not found" }));
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
else {
|
|
933
|
-
fs.writeFileSync(skillPath, content);
|
|
934
|
-
}
|
|
935
|
-
const { loadSkills } = await import("../services/skills.js");
|
|
936
|
-
loadSkills();
|
|
937
|
-
res.end(JSON.stringify({ ok: true }));
|
|
938
|
-
}
|
|
939
|
-
catch (e) {
|
|
940
|
-
res.statusCode = 400;
|
|
941
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
942
|
-
}
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
// POST /api/skills/delete — delete a skill
|
|
946
|
-
if (urlPath === "/api/skills/delete" && req.method === "POST") {
|
|
947
|
-
try {
|
|
948
|
-
const { id } = JSON.parse(body);
|
|
949
|
-
// Security: same raw-string id containment as /api/skills/create + /api/skills/update
|
|
950
|
-
if (typeof id !== "string" ||
|
|
951
|
-
id.includes("..") ||
|
|
952
|
-
id.includes("/") ||
|
|
953
|
-
id.includes("\\") ||
|
|
954
|
-
path.isAbsolute(id)) {
|
|
955
|
-
res.statusCode = 400;
|
|
956
|
-
res.end(JSON.stringify({ error: "Invalid skill id" }));
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
const skillDir = resolve(SKILLS_DIR, id);
|
|
960
|
-
const flatFile = resolve(SKILLS_DIR, id + ".md");
|
|
961
|
-
if (fs.existsSync(skillDir)) {
|
|
962
|
-
fs.rmSync(skillDir, { recursive: true });
|
|
963
|
-
}
|
|
964
|
-
else if (fs.existsSync(flatFile)) {
|
|
965
|
-
fs.unlinkSync(flatFile);
|
|
966
|
-
}
|
|
967
|
-
else {
|
|
968
|
-
res.statusCode = 404;
|
|
969
|
-
res.end(JSON.stringify({ error: "Skill not found" }));
|
|
970
|
-
return;
|
|
971
|
-
}
|
|
972
|
-
const { loadSkills } = await import("../services/skills.js");
|
|
973
|
-
loadSkills();
|
|
974
|
-
res.end(JSON.stringify({ ok: true }));
|
|
975
|
-
}
|
|
976
|
-
catch (e) {
|
|
977
|
-
res.statusCode = 400;
|
|
978
|
-
res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
979
|
-
}
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
// GET /api/config
|
|
983
|
-
if (urlPath === "/api/config") {
|
|
984
|
-
res.end(JSON.stringify({
|
|
985
|
-
providers: config.fallbackProviders,
|
|
986
|
-
primaryProvider: config.primaryProvider,
|
|
987
|
-
allowedUsers: config.allowedUsers,
|
|
988
|
-
hasKeys: {
|
|
989
|
-
groq: !!config.apiKeys.groq,
|
|
990
|
-
openai: !!config.apiKeys.openai,
|
|
991
|
-
google: !!config.apiKeys.google,
|
|
992
|
-
nvidia: !!config.apiKeys.nvidia,
|
|
993
|
-
openrouter: !!config.apiKeys.openrouter,
|
|
994
|
-
},
|
|
995
|
-
}));
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
998
|
-
// GET /api/sessions
|
|
999
|
-
if (urlPath === "/api/sessions") {
|
|
1000
|
-
const sessions = getAllSessions();
|
|
1001
|
-
const profiles = listProfiles();
|
|
1002
|
-
const data = Array.from(sessions.entries()).map(([key, session]) => {
|
|
1003
|
-
const userId = Number(key.split(":").pop());
|
|
1004
|
-
const profile = profiles.find(p => p.userId === userId);
|
|
1005
|
-
return {
|
|
1006
|
-
userId: key,
|
|
1007
|
-
name: profile?.name || `User ${key}`,
|
|
1008
|
-
username: profile?.username,
|
|
1009
|
-
messageCount: session.messageCount,
|
|
1010
|
-
toolUseCount: session.toolUseCount,
|
|
1011
|
-
totalCost: session.totalCost,
|
|
1012
|
-
totalInputTokens: session.totalInputTokens || 0,
|
|
1013
|
-
totalOutputTokens: session.totalOutputTokens || 0,
|
|
1014
|
-
effort: session.effort,
|
|
1015
|
-
startedAt: session.startedAt,
|
|
1016
|
-
lastActivity: session.lastActivity,
|
|
1017
|
-
historyLength: session.history.length,
|
|
1018
|
-
isProcessing: session.isProcessing,
|
|
1019
|
-
provider: Object.keys(session.queriesByProvider).join(", ") || "none",
|
|
1020
|
-
};
|
|
1021
|
-
});
|
|
1022
|
-
res.end(JSON.stringify({ sessions: data }));
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
// GET /api/sessions/:userId/history
|
|
1026
|
-
if (urlPath.match(/^\/api\/sessions\/\d+\/history$/)) {
|
|
1027
|
-
const userId = parseInt(urlPath.split("/")[3]);
|
|
1028
|
-
const session = getSession(userId);
|
|
1029
|
-
res.end(JSON.stringify({
|
|
1030
|
-
userId,
|
|
1031
|
-
history: session.history.map(h => ({ role: h.role, content: h.content.slice(0, 2000) })),
|
|
1032
|
-
}));
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
// GET /api/files?path=...
|
|
1036
|
-
if (urlPath === "/api/files") {
|
|
1037
|
-
const params = new URLSearchParams((req.url || "").split("?")[1] || "");
|
|
1038
|
-
const reqPath = params.get("path") || "";
|
|
1039
|
-
const basePath = resolve(BOT_ROOT, reqPath || ".");
|
|
1040
|
-
// Security: must be within BOT_ROOT
|
|
1041
|
-
if (!basePath.startsWith(BOT_ROOT)) {
|
|
1042
|
-
res.statusCode = 403;
|
|
1043
|
-
res.end(JSON.stringify({ error: "Access denied" }));
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
try {
|
|
1047
|
-
const stat = fs.statSync(basePath);
|
|
1048
|
-
if (stat.isDirectory()) {
|
|
1049
|
-
const entries = fs.readdirSync(basePath, { withFileTypes: true })
|
|
1050
|
-
.filter(e => !e.name.startsWith(".") && e.name !== "node_modules")
|
|
1051
|
-
.map(e => ({
|
|
1052
|
-
name: e.name,
|
|
1053
|
-
type: e.isDirectory() ? "dir" : "file",
|
|
1054
|
-
size: e.isFile() ? fs.statSync(resolve(basePath, e.name)).size : 0,
|
|
1055
|
-
modified: fs.statSync(resolve(basePath, e.name)).mtimeMs,
|
|
1056
|
-
}))
|
|
1057
|
-
.sort((a, b) => {
|
|
1058
|
-
if (a.type !== b.type)
|
|
1059
|
-
return a.type === "dir" ? -1 : 1;
|
|
1060
|
-
return a.name.localeCompare(b.name);
|
|
1061
|
-
});
|
|
1062
|
-
res.end(JSON.stringify({ path: reqPath || ".", entries }));
|
|
1063
|
-
}
|
|
1064
|
-
else {
|
|
1065
|
-
// Read file content — text files up to 500KB
|
|
1066
|
-
const ext = path.extname(basePath).toLowerCase();
|
|
1067
|
-
const textExts = new Set([
|
|
1068
|
-
".md", ".txt", ".json", ".js", ".ts", ".jsx", ".tsx", ".css", ".html", ".htm",
|
|
1069
|
-
".xml", ".svg", ".yml", ".yaml", ".toml", ".ini", ".cfg", ".conf", ".env",
|
|
1070
|
-
".sh", ".bash", ".zsh", ".fish", ".py", ".rb", ".go", ".rs", ".java", ".kt",
|
|
1071
|
-
".c", ".cpp", ".h", ".hpp", ".cs", ".php", ".sql", ".graphql", ".prisma",
|
|
1072
|
-
".dockerfile", ".gitignore", ".gitattributes", ".editorconfig", ".prettierrc",
|
|
1073
|
-
".eslintrc", ".babelrc", ".npmrc", ".nvmrc", ".lock", ".log", ".csv", ".tsv",
|
|
1074
|
-
".mjs", ".cjs", ".mts", ".cts", ".vue", ".svelte", ".astro",
|
|
1075
|
-
]);
|
|
1076
|
-
// Files without extension that match known names are always text
|
|
1077
|
-
const textNames = new Set([
|
|
1078
|
-
"dockerfile", "makefile", "procfile", "gemfile", "rakefile",
|
|
1079
|
-
"vagrantfile", "brewfile", "justfile", "taskfile", "cakefile",
|
|
1080
|
-
"license", "licence", "readme", "changelog", "authors", "contributors",
|
|
1081
|
-
]);
|
|
1082
|
-
const baseName = path.basename(basePath).toLowerCase();
|
|
1083
|
-
const isKnownTextName = textNames.has(baseName);
|
|
1084
|
-
const isText = textExts.has(ext) || isKnownTextName || (!ext && stat.size < 100_000);
|
|
1085
|
-
if (stat.size > 500_000) {
|
|
1086
|
-
res.end(JSON.stringify({ path: reqPath, content: `[File too large: ${(stat.size / 1024).toFixed(1)} KB — max 500 KB]`, size: stat.size }));
|
|
1087
|
-
}
|
|
1088
|
-
else if (isText) {
|
|
1089
|
-
try {
|
|
1090
|
-
const content = fs.readFileSync(basePath, "utf-8");
|
|
1091
|
-
// Quick binary check: if >10% null bytes, it's binary
|
|
1092
|
-
const nullCount = [...content.slice(0, 1000)].filter(c => c === "\0").length;
|
|
1093
|
-
if (nullCount > 100) {
|
|
1094
|
-
res.end(JSON.stringify({ path: reqPath, content: null, size: stat.size, binary: true }));
|
|
1095
|
-
}
|
|
1096
|
-
else {
|
|
1097
|
-
res.end(JSON.stringify({ path: reqPath, content, size: stat.size }));
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
catch {
|
|
1101
|
-
res.end(JSON.stringify({ path: reqPath, content: null, size: stat.size, binary: true }));
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
else {
|
|
1105
|
-
res.end(JSON.stringify({ path: reqPath, content: null, size: stat.size, binary: true }));
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
catch {
|
|
1110
|
-
res.statusCode = 404;
|
|
1111
|
-
res.end(JSON.stringify({ error: "Not found" }));
|
|
1112
|
-
}
|
|
1113
|
-
return;
|
|
1114
|
-
}
|
|
1115
|
-
// POST /api/files/save
|
|
1116
|
-
if (urlPath === "/api/files/save" && req.method === "POST") {
|
|
1117
|
-
try {
|
|
1118
|
-
const { path: filePath, content } = JSON.parse(body);
|
|
1119
|
-
const absPath = resolve(BOT_ROOT, filePath);
|
|
1120
|
-
if (!absPath.startsWith(BOT_ROOT)) {
|
|
1121
|
-
res.statusCode = 403;
|
|
1122
|
-
res.end(JSON.stringify({ error: "Access denied" }));
|
|
1123
|
-
return;
|
|
1124
|
-
}
|
|
1125
|
-
fs.writeFileSync(absPath, content);
|
|
1126
|
-
res.end(JSON.stringify({ ok: true }));
|
|
1127
|
-
}
|
|
1128
|
-
catch (err) {
|
|
1129
|
-
res.statusCode = 400;
|
|
1130
|
-
const error = err instanceof Error ? err.message : "Invalid request";
|
|
1131
|
-
res.end(JSON.stringify({ error }));
|
|
1132
|
-
}
|
|
1133
|
-
return;
|
|
1134
|
-
}
|
|
1135
|
-
// POST /api/files/delete
|
|
1136
|
-
if (urlPath === "/api/files/delete" && req.method === "POST") {
|
|
1137
|
-
try {
|
|
1138
|
-
const { path: filePath } = JSON.parse(body);
|
|
1139
|
-
const absPath = resolve(BOT_ROOT, filePath);
|
|
1140
|
-
if (!absPath.startsWith(BOT_ROOT)) {
|
|
1141
|
-
res.statusCode = 403;
|
|
1142
|
-
res.end(JSON.stringify({ error: "Access denied" }));
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
// Safety: don't allow deleting critical files
|
|
1146
|
-
const critical = [".env", "package.json", "tsconfig.json", "ecosystem.config.cjs"];
|
|
1147
|
-
const baseName = path.basename(absPath);
|
|
1148
|
-
if (critical.includes(baseName)) {
|
|
1149
|
-
res.statusCode = 403;
|
|
1150
|
-
res.end(JSON.stringify({ error: `${baseName} cannot be deleted (protected)` }));
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
if (!fs.existsSync(absPath)) {
|
|
1154
|
-
res.statusCode = 404;
|
|
1155
|
-
res.end(JSON.stringify({ error: "File not found" }));
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
const stat = fs.statSync(absPath);
|
|
1159
|
-
if (stat.isDirectory()) {
|
|
1160
|
-
res.statusCode = 400;
|
|
1161
|
-
res.end(JSON.stringify({ error: "Directories cannot be deleted" }));
|
|
1162
|
-
return;
|
|
1163
|
-
}
|
|
1164
|
-
fs.unlinkSync(absPath);
|
|
1165
|
-
res.end(JSON.stringify({ ok: true }));
|
|
1166
|
-
}
|
|
1167
|
-
catch (err) {
|
|
1168
|
-
res.statusCode = 400;
|
|
1169
|
-
const error = err instanceof Error ? err.message : "Invalid request";
|
|
1170
|
-
res.end(JSON.stringify({ error }));
|
|
1171
|
-
}
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
// POST /api/terminal
|
|
1175
|
-
if (urlPath === "/api/terminal" && req.method === "POST") {
|
|
1176
|
-
try {
|
|
1177
|
-
const { command } = JSON.parse(body);
|
|
1178
|
-
if (!command) {
|
|
1179
|
-
res.statusCode = 400;
|
|
1180
|
-
res.end(JSON.stringify({ error: "No command" }));
|
|
1181
|
-
return;
|
|
1182
|
-
}
|
|
1183
|
-
// Security: limit command length
|
|
1184
|
-
if (command.length > 10000) {
|
|
1185
|
-
res.statusCode = 400;
|
|
1186
|
-
res.end(JSON.stringify({ error: "Command too long (max 10000 chars)" }));
|
|
1187
|
-
return;
|
|
1188
|
-
}
|
|
1189
|
-
const cwd = typeof (JSON.parse(body)).cwd === "string" ? resolve(JSON.parse(body).cwd) : BOT_ROOT;
|
|
1190
|
-
const output = execSync(command, {
|
|
1191
|
-
cwd,
|
|
1192
|
-
stdio: "pipe",
|
|
1193
|
-
timeout: 120000,
|
|
1194
|
-
env: { ...process.env, PATH: process.env.PATH + ":/opt/homebrew/bin:/usr/local/bin" },
|
|
1195
|
-
}).toString();
|
|
1196
|
-
res.end(JSON.stringify({ output: output.slice(0, 100000) }));
|
|
1197
|
-
}
|
|
1198
|
-
catch (err) {
|
|
1199
|
-
const error = err;
|
|
1200
|
-
const stderr = error.stderr?.toString()?.trim() || "";
|
|
1201
|
-
res.end(JSON.stringify({ output: stderr || error.message, exitCode: 1 }));
|
|
1202
|
-
}
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
// GET /api/env — read .env keys (names only, values masked)
|
|
1206
|
-
if (urlPath === "/api/env") {
|
|
1207
|
-
try {
|
|
1208
|
-
const envContent = fs.existsSync(ENV_FILE) ? fs.readFileSync(ENV_FILE, "utf-8") : "";
|
|
1209
|
-
const lines = envContent.split("\n").filter(l => l.includes("=") && !l.startsWith("#"));
|
|
1210
|
-
const vars = lines.map(l => {
|
|
1211
|
-
const [key, ...rest] = l.split("=");
|
|
1212
|
-
const value = rest.join("=").trim();
|
|
1213
|
-
// Mask sensitive values
|
|
1214
|
-
const masked = key.includes("KEY") || key.includes("TOKEN") || key.includes("PASSWORD") || key.includes("SECRET")
|
|
1215
|
-
? (value.length > 4 ? value.slice(0, 4) + "..." + value.slice(-4) : "****")
|
|
1216
|
-
: value;
|
|
1217
|
-
return { key: key.trim(), value: masked, hasValue: value.length > 0 };
|
|
1218
|
-
});
|
|
1219
|
-
res.end(JSON.stringify({ vars }));
|
|
1220
|
-
}
|
|
1221
|
-
catch {
|
|
1222
|
-
res.end(JSON.stringify({ vars: [] }));
|
|
1223
|
-
}
|
|
1224
|
-
return;
|
|
1225
|
-
}
|
|
1226
|
-
// POST /api/env/set — update an env var
|
|
1227
|
-
if (urlPath === "/api/env/set" && req.method === "POST") {
|
|
1228
|
-
try {
|
|
1229
|
-
const { key, value } = JSON.parse(body);
|
|
1230
|
-
if (!key || typeof key !== "string" || !key.match(/^[A-Z_][A-Z0-9_]*$/)) {
|
|
1231
|
-
res.statusCode = 400;
|
|
1232
|
-
res.end(JSON.stringify({ error: "Invalid key name" }));
|
|
1233
|
-
return;
|
|
1234
|
-
}
|
|
1235
|
-
// M6: reject values containing newline characters — they allow injecting
|
|
1236
|
-
// extra .env lines (e.g. value="good\nEVIL=injected").
|
|
1237
|
-
if (typeof value === "string" && /[\n\r]/.test(value)) {
|
|
1238
|
-
res.statusCode = 400;
|
|
1239
|
-
res.end(JSON.stringify({ error: "Invalid value: newline characters not allowed" }));
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
let envContent = fs.existsSync(ENV_FILE) ? fs.readFileSync(ENV_FILE, "utf-8") : "";
|
|
1243
|
-
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
1244
|
-
if (regex.test(envContent)) {
|
|
1245
|
-
envContent = envContent.replace(regex, `${key}=${value}`);
|
|
1246
|
-
}
|
|
1247
|
-
else {
|
|
1248
|
-
envContent = envContent.trimEnd() + `\n${key}=${value}\n`;
|
|
1249
|
-
}
|
|
1250
|
-
// v4.12.2 — enforce 0o600 on .env
|
|
1251
|
-
writeSecure(ENV_FILE, envContent);
|
|
1252
|
-
res.end(JSON.stringify({ ok: true, note: "Restart required for changes to take effect" }));
|
|
1253
|
-
}
|
|
1254
|
-
catch {
|
|
1255
|
-
res.statusCode = 400;
|
|
1256
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
1257
|
-
}
|
|
1258
|
-
return;
|
|
1259
|
-
}
|
|
1260
|
-
// GET /api/soul — read SOUL.md
|
|
1261
|
-
if (urlPath === "/api/soul") {
|
|
1262
|
-
const content = getSoulContent();
|
|
1263
|
-
res.end(JSON.stringify({ content }));
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
// POST /api/soul/save — update SOUL.md
|
|
1267
|
-
if (urlPath === "/api/soul/save" && req.method === "POST") {
|
|
1268
|
-
try {
|
|
1269
|
-
const { content } = JSON.parse(body);
|
|
1270
|
-
const soulPath = SOUL_FILE;
|
|
1271
|
-
fs.writeFileSync(soulPath, content);
|
|
1272
|
-
reloadSoul();
|
|
1273
|
-
res.end(JSON.stringify({ ok: true }));
|
|
1274
|
-
}
|
|
1275
|
-
catch {
|
|
1276
|
-
res.statusCode = 400;
|
|
1277
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
1278
|
-
}
|
|
1279
|
-
return;
|
|
1280
|
-
}
|
|
1281
|
-
// GET /api/platforms — platform adapter status
|
|
1282
|
-
if (urlPath === "/api/platforms") {
|
|
1283
|
-
const platforms = [
|
|
1284
|
-
{ name: "Telegram", key: "BOT_TOKEN", icon: "📱", configured: !!process.env.BOT_TOKEN },
|
|
1285
|
-
{ name: "Discord", key: "DISCORD_TOKEN", icon: "🎮", configured: !!process.env.DISCORD_TOKEN },
|
|
1286
|
-
{ name: "WhatsApp", key: "WHATSAPP_ENABLED", icon: "💬", configured: process.env.WHATSAPP_ENABLED === "true" },
|
|
1287
|
-
{ name: "Signal", key: "SIGNAL_API_URL", icon: "🔒", configured: !!process.env.SIGNAL_API_URL },
|
|
1288
|
-
{ name: "Web UI", key: "WEB_PORT", icon: "🌐", configured: true },
|
|
1289
|
-
];
|
|
1290
|
-
res.end(JSON.stringify({ platforms }));
|
|
1291
|
-
return;
|
|
1292
|
-
}
|
|
1293
|
-
// POST /api/restart — restart the bot process
|
|
1294
|
-
if (urlPath === "/api/restart" && req.method === "POST") {
|
|
1295
|
-
const { scheduleGracefulRestart } = await import("../services/restart.js");
|
|
1296
|
-
res.end(JSON.stringify({ ok: true, note: "Restarting..." }));
|
|
1297
|
-
scheduleGracefulRestart(500);
|
|
1298
|
-
return;
|
|
1299
|
-
}
|
|
1300
|
-
// POST /api/chat/export — export chat history
|
|
1301
|
-
if (urlPath === "/api/chat/export" && req.method === "POST") {
|
|
1302
|
-
try {
|
|
1303
|
-
const { messages, format } = JSON.parse(body);
|
|
1304
|
-
if (format === "json") {
|
|
1305
|
-
res.setHeader("Content-Type", "application/json");
|
|
1306
|
-
res.end(JSON.stringify({ export: messages }, null, 2));
|
|
1307
|
-
}
|
|
1308
|
-
else {
|
|
1309
|
-
// Markdown
|
|
1310
|
-
const md = messages.map((m) => {
|
|
1311
|
-
const prefix = m.role === "user" ? "**Du:**" : m.role === "assistant" ? "**Alvin Bot:**" : "*System:*";
|
|
1312
|
-
const time = m.time ? ` _(${m.time})_` : "";
|
|
1313
|
-
return `${prefix}${time}\n${m.text}\n`;
|
|
1314
|
-
}).join("\n---\n\n");
|
|
1315
|
-
res.setHeader("Content-Type", "text/markdown");
|
|
1316
|
-
res.end(`# Chat Export — Alvin Bot\n_${new Date().toLocaleString("de-DE")}_\n\n---\n\n${md}`);
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
catch {
|
|
1320
|
-
res.statusCode = 400;
|
|
1321
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
1322
|
-
}
|
|
1323
|
-
return;
|
|
1324
|
-
}
|
|
1325
|
-
// ── WhatsApp Group Management API ────────────────────────────────────
|
|
1326
|
-
// GET /api/whatsapp/groups — list all WhatsApp groups (live from WA)
|
|
1327
|
-
if (urlPath === "/api/whatsapp/groups" && req.method === "GET") {
|
|
1328
|
-
try {
|
|
1329
|
-
const { getWhatsAppAdapter } = await import("../platforms/whatsapp.js");
|
|
1330
|
-
const adapter = getWhatsAppAdapter();
|
|
1331
|
-
if (!adapter) {
|
|
1332
|
-
res.end(JSON.stringify({ groups: [], error: "WhatsApp nicht verbunden" }));
|
|
1333
|
-
return;
|
|
1334
|
-
}
|
|
1335
|
-
const groups = await adapter.getGroups();
|
|
1336
|
-
res.end(JSON.stringify({ groups }));
|
|
1337
|
-
}
|
|
1338
|
-
catch (err) {
|
|
1339
|
-
res.end(JSON.stringify({ groups: [], error: String(err) }));
|
|
1340
|
-
}
|
|
1341
|
-
return;
|
|
1342
|
-
}
|
|
1343
|
-
// GET /api/whatsapp/groups/:id/participants — fetch group participants
|
|
1344
|
-
if (urlPath.match(/^\/api\/whatsapp\/groups\/[^/]+\/participants$/)) {
|
|
1345
|
-
try {
|
|
1346
|
-
const groupId = decodeURIComponent(urlPath.split("/")[4]);
|
|
1347
|
-
const { getWhatsAppAdapter } = await import("../platforms/whatsapp.js");
|
|
1348
|
-
const adapter = getWhatsAppAdapter();
|
|
1349
|
-
if (!adapter) {
|
|
1350
|
-
res.end(JSON.stringify({ participants: [], error: "WhatsApp nicht verbunden" }));
|
|
1351
|
-
return;
|
|
1352
|
-
}
|
|
1353
|
-
const participants = await adapter.getGroupParticipants(groupId);
|
|
1354
|
-
res.end(JSON.stringify({ participants }));
|
|
1355
|
-
}
|
|
1356
|
-
catch (err) {
|
|
1357
|
-
res.end(JSON.stringify({ participants: [], error: String(err) }));
|
|
1358
|
-
}
|
|
1359
|
-
return;
|
|
1360
|
-
}
|
|
1361
|
-
// GET /api/whatsapp/group-rules — get all configured group rules
|
|
1362
|
-
if (urlPath === "/api/whatsapp/group-rules" && req.method === "GET") {
|
|
1363
|
-
const { getGroupRules } = await import("../platforms/whatsapp.js");
|
|
1364
|
-
res.end(JSON.stringify({ rules: getGroupRules() }));
|
|
1365
|
-
return;
|
|
1366
|
-
}
|
|
1367
|
-
// POST /api/whatsapp/group-rules — create or update a group rule
|
|
1368
|
-
if (urlPath === "/api/whatsapp/group-rules" && req.method === "POST") {
|
|
1369
|
-
try {
|
|
1370
|
-
const rule = JSON.parse(body);
|
|
1371
|
-
if (!rule.groupId) {
|
|
1372
|
-
res.statusCode = 400;
|
|
1373
|
-
res.end(JSON.stringify({ error: "groupId ist erforderlich" }));
|
|
1374
|
-
return;
|
|
1375
|
-
}
|
|
1376
|
-
const { upsertGroupRule } = await import("../platforms/whatsapp.js");
|
|
1377
|
-
const saved = upsertGroupRule(rule);
|
|
1378
|
-
res.end(JSON.stringify({ ok: true, rule: saved }));
|
|
1379
|
-
}
|
|
1380
|
-
catch (err) {
|
|
1381
|
-
res.statusCode = 400;
|
|
1382
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
1383
|
-
}
|
|
1384
|
-
return;
|
|
1385
|
-
}
|
|
1386
|
-
// DELETE /api/whatsapp/group-rules/:id — delete a group rule
|
|
1387
|
-
if (urlPath.match(/^\/api\/whatsapp\/group-rules\//) && req.method === "DELETE") {
|
|
1388
|
-
// Pattern route — gate here since EXPOSED_DANGEROUS_ROUTES can't express :id
|
|
1389
|
-
if (isExposedWithoutPassword()) {
|
|
1390
|
-
res.statusCode = 403;
|
|
1391
|
-
res.end(JSON.stringify({ error: "refused: web exposed without WEB_PASSWORD" }));
|
|
1392
|
-
return;
|
|
1393
|
-
}
|
|
1394
|
-
const groupId = decodeURIComponent(urlPath.split("/").slice(4).join("/"));
|
|
1395
|
-
const { deleteGroupRule } = await import("../platforms/whatsapp.js");
|
|
1396
|
-
const ok = deleteGroupRule(groupId);
|
|
1397
|
-
res.end(JSON.stringify({ ok }));
|
|
1398
|
-
return;
|
|
1399
|
-
}
|
|
1400
|
-
res.statusCode = 404;
|
|
1401
|
-
res.end(JSON.stringify({ error: "Not found" }));
|
|
1402
|
-
}
|
|
1403
|
-
// ── WebSocket Chat ──────────────────────────────────────
|
|
1404
|
-
// Set of all currently connected chat WebSocket clients (excluding canvas).
|
|
1405
|
-
// Populated on connect, cleaned up on close. Used to forward Telegram
|
|
1406
|
-
// activity to every observer.
|
|
1407
|
-
const chatClients = new Set();
|
|
1408
|
-
/**
|
|
1409
|
-
* Wire the broadcast bus once at module load. The bus is singleton, so
|
|
1410
|
-
* subscribing here means every Telegram message fan-outs to every connected
|
|
1411
|
-
* chat client — without any per-connection re-subscription.
|
|
1412
|
-
*/
|
|
1413
|
-
broadcast.on("user_msg", (payload) => {
|
|
1414
|
-
if (payload.platform !== "telegram")
|
|
1415
|
-
return; // v4.5.0: telegram only for now
|
|
1416
|
-
const json = JSON.stringify({
|
|
1417
|
-
type: "mirror:user_msg",
|
|
1418
|
-
text: payload.text,
|
|
1419
|
-
platform: payload.platform,
|
|
1420
|
-
userName: payload.userName,
|
|
1421
|
-
ts: payload.ts,
|
|
1422
|
-
});
|
|
1423
|
-
for (const client of chatClients) {
|
|
1424
|
-
if (client.readyState === WebSocket.OPEN)
|
|
1425
|
-
client.send(json);
|
|
1426
|
-
}
|
|
1427
|
-
});
|
|
1428
|
-
broadcast.on("response_start", (payload) => {
|
|
1429
|
-
if (payload.platform !== "telegram")
|
|
1430
|
-
return;
|
|
1431
|
-
const json = JSON.stringify({ type: "mirror:response_start", platform: payload.platform, ts: payload.ts });
|
|
1432
|
-
for (const client of chatClients) {
|
|
1433
|
-
if (client.readyState === WebSocket.OPEN)
|
|
1434
|
-
client.send(json);
|
|
1435
|
-
}
|
|
1436
|
-
});
|
|
1437
|
-
broadcast.on("response_delta", (payload) => {
|
|
1438
|
-
if (payload.platform !== "telegram")
|
|
1439
|
-
return;
|
|
1440
|
-
const json = JSON.stringify({ type: "mirror:response_delta", delta: payload.delta, platform: payload.platform, ts: payload.ts });
|
|
1441
|
-
for (const client of chatClients) {
|
|
1442
|
-
if (client.readyState === WebSocket.OPEN)
|
|
1443
|
-
client.send(json);
|
|
1444
|
-
}
|
|
1445
|
-
});
|
|
1446
|
-
broadcast.on("response_done", (payload) => {
|
|
1447
|
-
if (payload.platform !== "telegram")
|
|
1448
|
-
return;
|
|
1449
|
-
const json = JSON.stringify({ type: "mirror:response_done", cost: payload.cost, platform: payload.platform, ts: payload.ts });
|
|
1450
|
-
for (const client of chatClients) {
|
|
1451
|
-
if (client.readyState === WebSocket.OPEN)
|
|
1452
|
-
client.send(json);
|
|
1453
|
-
}
|
|
1454
|
-
});
|
|
1455
|
-
function handleWebSocket(wss) {
|
|
1456
|
-
wss.on("connection", (ws, req) => {
|
|
1457
|
-
// Auth check
|
|
1458
|
-
if (WEB_PASSWORD && !checkAuth(req)) {
|
|
1459
|
-
ws.close(4001, "Not authenticated");
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
// Canvas WebSocket — separate handler
|
|
1463
|
-
const wsUrl = req.url || "/";
|
|
1464
|
-
if (wsUrl === "/canvas/ws") {
|
|
1465
|
-
addCanvasClient(ws);
|
|
1466
|
-
return;
|
|
1467
|
-
}
|
|
1468
|
-
console.log("WebUI: client connected");
|
|
1469
|
-
chatClients.add(ws);
|
|
1470
|
-
ws.on("message", async (data) => {
|
|
1471
|
-
try {
|
|
1472
|
-
const msg = JSON.parse(data.toString());
|
|
1473
|
-
if (msg.type === "chat") {
|
|
1474
|
-
let { text, effort, file } = msg;
|
|
1475
|
-
// v4.5.0: session routing. The client (TUI/WebUI) tells us which
|
|
1476
|
-
// session it wants its message to go into. Supported targets:
|
|
1477
|
-
// - "tui" → use msg.sessionKey (e.g. "tui:local" or
|
|
1478
|
-
// "tui:ephemeral:…"). Isolated from Telegram.
|
|
1479
|
-
// - "telegram" → route into the primary Telegram user's session.
|
|
1480
|
-
// Responses go back to the client AND to the
|
|
1481
|
-
// actual Telegram chat via the broadcast bus.
|
|
1482
|
-
// - undefined → backwards-compatible: default to the primary
|
|
1483
|
-
// allowed user's session (old behavior).
|
|
1484
|
-
const target = msg.target;
|
|
1485
|
-
const telegramUserId = config.allowedUsers[0] || 0;
|
|
1486
|
-
let sessionKey;
|
|
1487
|
-
if (target === "tui" && typeof msg.sessionKey === "string" && msg.sessionKey.startsWith("tui:")) {
|
|
1488
|
-
sessionKey = msg.sessionKey;
|
|
1489
|
-
}
|
|
1490
|
-
else if (target === "telegram") {
|
|
1491
|
-
sessionKey = telegramUserId;
|
|
1492
|
-
}
|
|
1493
|
-
else {
|
|
1494
|
-
sessionKey = telegramUserId; // backwards compat
|
|
1495
|
-
}
|
|
1496
|
-
// Handle file upload — save to temp and reference in prompt
|
|
1497
|
-
if (file?.dataUrl && file?.name) {
|
|
1498
|
-
try {
|
|
1499
|
-
const dataDir = resolve(DATA_DIR, "web-uploads");
|
|
1500
|
-
if (!fs.existsSync(dataDir))
|
|
1501
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
1502
|
-
const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1503
|
-
const filePath = resolve(dataDir, `${Date.now()}_${safeName}`);
|
|
1504
|
-
const base64Data = file.dataUrl.split(",")[1] || file.dataUrl;
|
|
1505
|
-
fs.writeFileSync(filePath, Buffer.from(base64Data, "base64"));
|
|
1506
|
-
// Replace placeholder with actual file path
|
|
1507
|
-
text = text.replace(/\[File attached:.*?\]/, `[File saved: ${filePath}]`);
|
|
1508
|
-
}
|
|
1509
|
-
catch (err) {
|
|
1510
|
-
console.error("WebUI file upload error:", err);
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
const registry = getRegistry();
|
|
1514
|
-
const activeProvider = registry.getActive();
|
|
1515
|
-
const isSDK = activeProvider.config.type === "claude-sdk";
|
|
1516
|
-
const session = getSession(sessionKey);
|
|
1517
|
-
const queryOpts = {
|
|
1518
|
-
prompt: text,
|
|
1519
|
-
systemPrompt: buildSystemPrompt(isSDK, session.language, target === "telegram" ? "telegram" : "web-dashboard"),
|
|
1520
|
-
workingDir: session.workingDir,
|
|
1521
|
-
effort: effort || session.effort,
|
|
1522
|
-
sessionId: isSDK ? session.sessionId : null,
|
|
1523
|
-
history: !isSDK ? session.history : undefined,
|
|
1524
|
-
};
|
|
1525
|
-
let gotDone = false;
|
|
1526
|
-
let finalText = ""; // v4.5.0: capture the final response for target=telegram relay
|
|
1527
|
-
try {
|
|
1528
|
-
// Stream response
|
|
1529
|
-
for await (const chunk of registry.queryWithFallback(queryOpts)) {
|
|
1530
|
-
if (ws.readyState !== WebSocket.OPEN)
|
|
1531
|
-
break;
|
|
1532
|
-
switch (chunk.type) {
|
|
1533
|
-
case "text":
|
|
1534
|
-
if (chunk.text)
|
|
1535
|
-
finalText = chunk.text;
|
|
1536
|
-
ws.send(JSON.stringify({ type: "text", text: chunk.text, delta: chunk.delta }));
|
|
1537
|
-
break;
|
|
1538
|
-
case "tool_use":
|
|
1539
|
-
ws.send(JSON.stringify({ type: "tool", name: chunk.toolName, input: chunk.toolInput }));
|
|
1540
|
-
break;
|
|
1541
|
-
case "done":
|
|
1542
|
-
gotDone = true;
|
|
1543
|
-
if (chunk.text)
|
|
1544
|
-
finalText = chunk.text;
|
|
1545
|
-
if (chunk.sessionId)
|
|
1546
|
-
session.sessionId = chunk.sessionId;
|
|
1547
|
-
if (chunk.costUsd)
|
|
1548
|
-
session.totalCost += chunk.costUsd;
|
|
1549
|
-
if (chunk.inputTokens)
|
|
1550
|
-
session.totalInputTokens = (session.totalInputTokens || 0) + chunk.inputTokens;
|
|
1551
|
-
if (chunk.outputTokens)
|
|
1552
|
-
session.totalOutputTokens = (session.totalOutputTokens || 0) + chunk.outputTokens;
|
|
1553
|
-
ws.send(JSON.stringify({
|
|
1554
|
-
type: "done", cost: chunk.costUsd, sessionId: chunk.sessionId,
|
|
1555
|
-
inputTokens: chunk.inputTokens, outputTokens: chunk.outputTokens,
|
|
1556
|
-
sessionTokens: { input: session.totalInputTokens || 0, output: session.totalOutputTokens || 0 },
|
|
1557
|
-
}));
|
|
1558
|
-
break;
|
|
1559
|
-
case "error":
|
|
1560
|
-
ws.send(JSON.stringify({ type: "error", error: chunk.error }));
|
|
1561
|
-
gotDone = true; // error counts as done
|
|
1562
|
-
break;
|
|
1563
|
-
case "fallback":
|
|
1564
|
-
ws.send(JSON.stringify({ type: "fallback", from: chunk.failedProvider, to: chunk.providerName }));
|
|
1565
|
-
break;
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
// Ensure we always send done (in case stream ended without done/error chunk)
|
|
1569
|
-
if (!gotDone && ws.readyState === WebSocket.OPEN) {
|
|
1570
|
-
ws.send(JSON.stringify({ type: "done", cost: 0 }));
|
|
1571
|
-
}
|
|
1572
|
-
// v4.5.0: if the user typed in the TUI with target=telegram, we
|
|
1573
|
-
// must also post the bot's final response to the actual Telegram
|
|
1574
|
-
// chat so the continuity is preserved from the Telegram side.
|
|
1575
|
-
// (Telegram bots cannot forge user messages, so only the
|
|
1576
|
-
// response lands in the chat — the user prompt itself stays
|
|
1577
|
-
// in the TUI.)
|
|
1578
|
-
if (target === "telegram" && finalText.trim()) {
|
|
1579
|
-
try {
|
|
1580
|
-
const dq = await import("../services/delivery-queue.js");
|
|
1581
|
-
dq.enqueue("telegram", String(telegramUserId), finalText);
|
|
1582
|
-
}
|
|
1583
|
-
catch (err) {
|
|
1584
|
-
console.error("WebUI → Telegram relay failed:", err);
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
catch (streamErr) {
|
|
1589
|
-
const errMsg = streamErr instanceof Error ? streamErr.message : String(streamErr);
|
|
1590
|
-
console.error("WebUI stream error:", errMsg);
|
|
1591
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
1592
|
-
ws.send(JSON.stringify({ type: "error", error: errMsg }));
|
|
1593
|
-
if (!gotDone) {
|
|
1594
|
-
ws.send(JSON.stringify({ type: "done", cost: 0 }));
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
if (msg.type === "reset") {
|
|
1600
|
-
// v4.5.0: reset the target session, not a hardcoded one.
|
|
1601
|
-
const target = msg.target;
|
|
1602
|
-
const telegramUserId = config.allowedUsers[0] || 0;
|
|
1603
|
-
let resetKey;
|
|
1604
|
-
if (target === "tui" && typeof msg.sessionKey === "string" && msg.sessionKey.startsWith("tui:")) {
|
|
1605
|
-
resetKey = msg.sessionKey;
|
|
1606
|
-
}
|
|
1607
|
-
else {
|
|
1608
|
-
resetKey = telegramUserId;
|
|
1609
|
-
}
|
|
1610
|
-
resetSession(resetKey);
|
|
1611
|
-
ws.send(JSON.stringify({ type: "reset", ok: true }));
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
catch (err) {
|
|
1615
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
1616
|
-
ws.send(JSON.stringify({ type: "error", error }));
|
|
1617
|
-
}
|
|
1618
|
-
});
|
|
1619
|
-
ws.on("close", () => {
|
|
1620
|
-
console.log("WebUI: client disconnected");
|
|
1621
|
-
chatClients.delete(ws);
|
|
1622
|
-
});
|
|
1623
|
-
});
|
|
1624
|
-
}
|
|
1625
|
-
// ── Start Server ────────────────────────────────────────
|
|
1626
|
-
/**
|
|
1627
|
-
* HTTP request handler for the web UI. Hoisted to a top-level function
|
|
1628
|
-
* so every bind attempt can create a fresh http.Server without
|
|
1629
|
-
* rebuilding the handler closure.
|
|
1630
|
-
*/
|
|
1631
|
-
function handleWebRequest(req, res) {
|
|
1632
|
-
let body = "";
|
|
1633
|
-
req.on("data", (chunk) => { body += chunk; });
|
|
1634
|
-
req.on("end", () => {
|
|
1635
|
-
const urlPath = (req.url || "/").split("?")[0];
|
|
1636
|
-
// OpenAI-compatible API (/v1/chat/completions, /v1/models)
|
|
1637
|
-
if (urlPath.startsWith("/v1/")) {
|
|
1638
|
-
handleOpenAICompat(req, res, urlPath, body);
|
|
1639
|
-
return;
|
|
1640
|
-
}
|
|
1641
|
-
// API routes
|
|
1642
|
-
if (urlPath.startsWith("/api/")) {
|
|
1643
|
-
handleAPI(req, res, urlPath, body);
|
|
1644
|
-
return;
|
|
1645
|
-
}
|
|
1646
|
-
// v5.7.0 — internal bot-to-bot routes (detached sub-agent exit
|
|
1647
|
-
// push). Dispatched through handleAPI, which bearer-auths it and
|
|
1648
|
-
// returns before the cookie-auth gate. Kept off the /api/ prefix so
|
|
1649
|
-
// it is never surfaced in the Web UI route surface.
|
|
1650
|
-
if (urlPath.startsWith("/internal/")) {
|
|
1651
|
-
handleAPI(req, res, urlPath, body);
|
|
1652
|
-
return;
|
|
1653
|
-
}
|
|
1654
|
-
// Auth page (if password set and not authenticated)
|
|
1655
|
-
if (WEB_PASSWORD && !checkAuth(req) && urlPath !== "/login.html") {
|
|
1656
|
-
res.writeHead(302, { Location: "/login.html" });
|
|
1657
|
-
res.end();
|
|
1658
|
-
return;
|
|
1659
|
-
}
|
|
1660
|
-
// Canvas UI
|
|
1661
|
-
if (urlPath === "/canvas") {
|
|
1662
|
-
const canvasFile = resolve(PUBLIC_DIR, "canvas.html");
|
|
1663
|
-
try {
|
|
1664
|
-
const content = fs.readFileSync(canvasFile);
|
|
1665
|
-
res.setHeader("Content-Type", "text/html");
|
|
1666
|
-
res.end(content);
|
|
1667
|
-
}
|
|
1668
|
-
catch {
|
|
1669
|
-
res.statusCode = 404;
|
|
1670
|
-
res.end("Not found");
|
|
1671
|
-
}
|
|
1672
|
-
return;
|
|
1673
|
-
}
|
|
1674
|
-
// Static files
|
|
1675
|
-
let filePath = urlPath === "/" ? "/index.html" : urlPath;
|
|
1676
|
-
filePath = resolve(PUBLIC_DIR, filePath.slice(1));
|
|
1677
|
-
// Security: prevent path traversal
|
|
1678
|
-
if (!filePath.startsWith(PUBLIC_DIR)) {
|
|
1679
|
-
res.statusCode = 403;
|
|
1680
|
-
res.end("Forbidden");
|
|
1681
|
-
return;
|
|
1682
|
-
}
|
|
1683
|
-
try {
|
|
1684
|
-
const content = fs.readFileSync(filePath);
|
|
1685
|
-
const ext = path.extname(filePath);
|
|
1686
|
-
res.setHeader("Content-Type", MIME[ext] || "application/octet-stream");
|
|
1687
|
-
res.end(content);
|
|
1688
|
-
}
|
|
1689
|
-
catch {
|
|
1690
|
-
res.statusCode = 404;
|
|
1691
|
-
res.end("Not found");
|
|
1692
|
-
}
|
|
1693
|
-
});
|
|
1694
|
-
}
|
|
1695
|
-
/**
|
|
1696
|
-
* Kick off the web-UI bind loop. NEVER throws, NEVER blocks.
|
|
1697
|
-
*
|
|
1698
|
-
* History: earlier versions returned an http.Server synchronously and
|
|
1699
|
-
* let listen() errors bubble up as uncaught exceptions — a colleague
|
|
1700
|
-
* flagged this on 2026-04-13 after spending months fighting the exact
|
|
1701
|
-
* same bug on a parallel OpenClaw fork. Their resolution: "the gateway
|
|
1702
|
-
* is a feature, not core. Decouple it."
|
|
1703
|
-
*
|
|
1704
|
-
* New contract:
|
|
1705
|
-
* - Returns `void` immediately. The actual bind happens asynchronously.
|
|
1706
|
-
* - If port 3100 is busy, tries 3101…3119 in sequence (same as before).
|
|
1707
|
-
* - If ALL 20 ports are busy, schedules a background retry at 3100
|
|
1708
|
-
* in `BACKGROUND_RETRY_MS` — keeps trying forever until success
|
|
1709
|
-
* or stopWebServer() is called.
|
|
1710
|
-
* - Any non-EADDRINUSE error also falls through to background retry.
|
|
1711
|
-
* - Each attempt uses a FRESH http.Server to avoid node's fragile
|
|
1712
|
-
* "listen-called-twice" state-recycling behaviour.
|
|
1713
|
-
* - The main Telegram bot is completely independent of this — if the
|
|
1714
|
-
* web UI never binds, the bot still answers messages.
|
|
1715
|
-
*/
|
|
1716
|
-
export function startWebServer() {
|
|
1717
|
-
stopRequested = false;
|
|
1718
|
-
scheduleBindAttempt(parseInt(process.env.WEB_PORT || "3100", 10), 0);
|
|
1719
|
-
}
|
|
1720
|
-
function scheduleBindAttempt(port, attempt) {
|
|
1721
|
-
if (stopRequested)
|
|
1722
|
-
return;
|
|
1723
|
-
// Read WEB_PORT live every time rather than closing over the
|
|
1724
|
-
// module-load value, so tests that change process.env.WEB_PORT
|
|
1725
|
-
// between runs see the new port.
|
|
1726
|
-
const originalPort = parseInt(process.env.WEB_PORT || "3100");
|
|
1727
|
-
// Fresh server for each attempt. Recycling a server that has already
|
|
1728
|
-
// emitted an EADDRINUSE error has produced "Listen method has been
|
|
1729
|
-
// called more than once" crashes in the wild.
|
|
1730
|
-
//
|
|
1731
|
-
// IMPORTANT: do NOT attach the WebSocketServer yet. The `ws` library
|
|
1732
|
-
// installs its own event plumbing on the http.Server in its
|
|
1733
|
-
// constructor, which causes bind errors to escape as uncaught
|
|
1734
|
-
// exceptions. We only attach it AFTER listen() has succeeded.
|
|
1735
|
-
const server = http.createServer(handleWebRequest);
|
|
1736
|
-
// Double-invocation guard: on some Node versions `server.listen`
|
|
1737
|
-
// both throws synchronously AND emits an `error` event for the same
|
|
1738
|
-
// bind failure. Without the guard we'd climb the ladder twice in
|
|
1739
|
-
// parallel and end up with two retry cascades racing each other.
|
|
1740
|
-
let handled = false;
|
|
1741
|
-
const cleanupDeadAttempt = () => {
|
|
1742
|
-
try {
|
|
1743
|
-
server.removeAllListeners("error");
|
|
1744
|
-
}
|
|
1745
|
-
catch { /* ignore */ }
|
|
1746
|
-
try {
|
|
1747
|
-
server.close(() => { });
|
|
1748
|
-
}
|
|
1749
|
-
catch { /* ignore */ }
|
|
1750
|
-
};
|
|
1751
|
-
const handleBindFailure = (err) => {
|
|
1752
|
-
if (handled)
|
|
1753
|
-
return;
|
|
1754
|
-
handled = true;
|
|
1755
|
-
cleanupDeadAttempt();
|
|
1756
|
-
if (stopRequested)
|
|
1757
|
-
return;
|
|
1758
|
-
const action = decideNextBindAction(err, attempt, {
|
|
1759
|
-
originalPort,
|
|
1760
|
-
maxPortTries: MAX_PORT_TRIES,
|
|
1761
|
-
backgroundRetryMs: BACKGROUND_RETRY_MS,
|
|
1762
|
-
});
|
|
1763
|
-
if (action.type === "retry-port") {
|
|
1764
|
-
console.warn(`[web] port ${port} busy (${err.code || err.message}) — trying ${action.port}`);
|
|
1765
|
-
scheduleBindAttempt(action.port, action.attempt);
|
|
1766
|
-
return;
|
|
1767
|
-
}
|
|
1768
|
-
// action.type === "retry-background"
|
|
1769
|
-
console.warn(`[web] bind failed (${err.code || err.message}) — ` +
|
|
1770
|
-
`backing off ${action.delayMs / 1000}s then retrying port ${action.port}. ` +
|
|
1771
|
-
`Bot is unaffected; Telegram remains live.`);
|
|
1772
|
-
bindRetryTimer = setTimeout(() => {
|
|
1773
|
-
bindRetryTimer = null;
|
|
1774
|
-
scheduleBindAttempt(action.port, 0);
|
|
1775
|
-
}, action.delayMs);
|
|
1776
|
-
};
|
|
1777
|
-
// Use `on` (not `once`) so a pathological server that emits two
|
|
1778
|
-
// error events for a single failure doesn't leave the second one
|
|
1779
|
-
// uncaught. The `handled` guard makes the handler idempotent.
|
|
1780
|
-
server.on("error", handleBindFailure);
|
|
1781
|
-
// Defensive try/catch — `server.listen()` usually emits async errors,
|
|
1782
|
-
// but certain Node versions + edge cases (already-listening server,
|
|
1783
|
-
// invalid backlog, kernel hiccup) can throw synchronously. Catch here
|
|
1784
|
-
// so the main routine never crashes during web-UI bind.
|
|
1785
|
-
try {
|
|
1786
|
-
// v4.20.2 — bind to config.webHost (default 127.0.0.1) so the Web UI
|
|
1787
|
-
// is loopback-only unless the operator opts in by setting WEB_HOST=0.0.0.0.
|
|
1788
|
-
// Empty/"*" maps to all interfaces.
|
|
1789
|
-
const bindHost = (config.webHost === "*" || config.webHost === "") ? undefined : config.webHost;
|
|
1790
|
-
server.listen(port, bindHost, () => {
|
|
1791
|
-
if (handled)
|
|
1792
|
-
return; // Should be impossible; paranoia.
|
|
1793
|
-
handled = true;
|
|
1794
|
-
// Now — and only now — attach the WebSocketServer. Before the
|
|
1795
|
-
// bind succeeded, the ws library's constructor would hijack the
|
|
1796
|
-
// http.Server's error event chain and let EADDRINUSE escape as
|
|
1797
|
-
// uncaught. Post-bind is safe.
|
|
1798
|
-
const wss = new WebSocketServer({ server });
|
|
1799
|
-
handleWebSocket(wss);
|
|
1800
|
-
currentServer = server;
|
|
1801
|
-
wsServerRef = wss;
|
|
1802
|
-
actualWebPort = port;
|
|
1803
|
-
// Remove the bind error handler — post-listen errors (socket
|
|
1804
|
-
// errors, close events) should not kick off a spurious retry
|
|
1805
|
-
// cycle. Install a quiet logger for any stray error events so
|
|
1806
|
-
// they can't escape as uncaught.
|
|
1807
|
-
server.removeListener("error", handleBindFailure);
|
|
1808
|
-
server.on("error", (err) => {
|
|
1809
|
-
console.warn(`[web] post-bind server error (ignored): ${err.message}`);
|
|
1810
|
-
});
|
|
1811
|
-
const bindLabel = bindHost && bindHost !== "127.0.0.1" && bindHost !== "::1"
|
|
1812
|
-
? `http://${bindHost}:${actualWebPort}` + (bindHost === "0.0.0.0" ? " (LAN-reachable)" : "")
|
|
1813
|
-
: `http://localhost:${actualWebPort}`;
|
|
1814
|
-
console.log(`🌐 Web UI: ${bindLabel}`);
|
|
1815
|
-
if (actualWebPort !== originalPort) {
|
|
1816
|
-
console.log(` (Port ${originalPort} was busy, using ${actualWebPort} instead)`);
|
|
1817
|
-
}
|
|
1818
|
-
if (isExposedWithoutPassword()) {
|
|
1819
|
-
// Upgrade from warn to a clear one-time log: the hard gate (above in
|
|
1820
|
-
// handleAPI) already blocks every mutating/exec route in this state,
|
|
1821
|
-
// so this message explains why those calls are being refused.
|
|
1822
|
-
console.log("[web] Non-loopback host with no WEB_PASSWORD: mutating/exec endpoints are disabled " +
|
|
1823
|
-
"(hard-refused 403). Set WEB_PASSWORD in ~/.alvin-bot/.env to unlock them, " +
|
|
1824
|
-
"or restrict to loopback with WEB_HOST=127.0.0.1.");
|
|
1825
|
-
}
|
|
1826
|
-
});
|
|
1827
|
-
}
|
|
1828
|
-
catch (err) {
|
|
1829
|
-
handleBindFailure(err);
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
/**
|
|
1833
|
-
* Gracefully close a specific http.Server — the low-level building
|
|
1834
|
-
* block. Exported for tests and for any future callers that manage
|
|
1835
|
-
* their own servers. Production bot code uses `stopWebServer()` below
|
|
1836
|
-
* which operates on the module-global current server instead.
|
|
1837
|
-
*
|
|
1838
|
-
* What this does:
|
|
1839
|
-
* 1. Force-close idle keep-alive sockets (Node 18.2+).
|
|
1840
|
-
* 2. Force-close active open requests (long-poll clients).
|
|
1841
|
-
* 3. Await `server.close()` so the listening socket is truly freed.
|
|
1842
|
-
*
|
|
1843
|
-
* Safe to call on already-closed, never-listened, or mid-listen servers.
|
|
1844
|
-
* Never throws.
|
|
1845
|
-
*/
|
|
1846
|
-
export async function closeHttpServerGracefully(server) {
|
|
1847
|
-
if (!server.listening)
|
|
1848
|
-
return;
|
|
1849
|
-
try {
|
|
1850
|
-
const s = server;
|
|
1851
|
-
if (typeof s.closeIdleConnections === "function")
|
|
1852
|
-
s.closeIdleConnections();
|
|
1853
|
-
if (typeof s.closeAllConnections === "function")
|
|
1854
|
-
s.closeAllConnections();
|
|
1855
|
-
}
|
|
1856
|
-
catch { /* ignore */ }
|
|
1857
|
-
await new Promise((resolve) => {
|
|
1858
|
-
server.close(() => resolve());
|
|
1859
|
-
});
|
|
1860
|
-
}
|
|
1861
|
-
/**
|
|
1862
|
-
* Stop the web server: cancel any pending background-retry, close
|
|
1863
|
-
* WebSocket clients, then gracefully close the HTTP server.
|
|
1864
|
-
*
|
|
1865
|
-
* Idempotent — safe to call multiple times, and safe to call before
|
|
1866
|
-
* startWebServer() ever successfully bound. Never throws.
|
|
1867
|
-
*/
|
|
1868
|
-
export async function stopWebServer() {
|
|
1869
|
-
stopRequested = true;
|
|
1870
|
-
// Cancel any pending background-retry timer so a late retry doesn't
|
|
1871
|
-
// grab the port AFTER we thought we'd shut everything down.
|
|
1872
|
-
if (bindRetryTimer) {
|
|
1873
|
-
clearTimeout(bindRetryTimer);
|
|
1874
|
-
bindRetryTimer = null;
|
|
1875
|
-
}
|
|
1876
|
-
// Tear down the WebSocket server first so its sockets can't keep
|
|
1877
|
-
// the underlying http.Server alive.
|
|
1878
|
-
if (wsServerRef) {
|
|
1879
|
-
try {
|
|
1880
|
-
for (const client of wsServerRef.clients) {
|
|
1881
|
-
try {
|
|
1882
|
-
client.terminate();
|
|
1883
|
-
}
|
|
1884
|
-
catch { /* ignore */ }
|
|
1885
|
-
}
|
|
1886
|
-
await new Promise((resolve) => wsServerRef.close(() => resolve()));
|
|
1887
|
-
}
|
|
1888
|
-
catch { /* ignore */ }
|
|
1889
|
-
wsServerRef = null;
|
|
1890
|
-
}
|
|
1891
|
-
if (currentServer) {
|
|
1892
|
-
try {
|
|
1893
|
-
await closeHttpServerGracefully(currentServer);
|
|
1894
|
-
}
|
|
1895
|
-
catch { /* ignore */ }
|
|
1896
|
-
currentServer = null;
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
/** Get the actual port the Web UI is running on. */
|
|
1900
|
-
export function getWebPort() {
|
|
1901
|
-
return actualWebPort;
|
|
1902
|
-
}
|
|
1
|
+
const _0x406ff9=_0x51ee,_0x5471aa=_0x51ee;(function(_0x50c314,_0x50b686){const _0xa38023=_0x51ee,_0x212872=_0x51ee,_0x40d45e=_0x50c314();while(!![]){try{const _0x1ff22f=parseInt(_0xa38023(0x140))/(-0x290*0x2+-0x1*-0x51+0x2c*0x1c)+-parseInt(_0xa38023(0x122))/(0xcab+-0x5*-0x60a+0x45*-0x9f)+-parseInt(_0x212872(0x84))/(-0x23*0x67+-0x1435*-0x1+0x139*-0x5)*(-parseInt(_0x212872(0xc4))/(-0x14*0x4f+-0x1d7+0x807))+parseInt(_0xa38023(0x14a))/(0xb8c+0x2301+-0x2*0x1744)+-parseInt(_0xa38023(0x167))/(-0x2c9+0x60c+0x1*-0x33d)*(parseInt(_0x212872(0x26a))/(0x1a8a+0x1d*-0xc+0x1927*-0x1))+-parseInt(_0xa38023(0x221))/(-0x13ed+0x176b+0x1*-0x376)*(-parseInt(_0x212872(0x163))/(0x116*0x19+0x921+-0x243e))+-parseInt(_0xa38023(0x87))/(0x6b*0x1f+0xeb9+-0x1ba4)*(-parseInt(_0x212872(0x262))/(0x1711+0x1*0x24e4+-0x2*0x1df5));if(_0x1ff22f===_0x50b686)break;else _0x40d45e['push'](_0x40d45e['shift']());}catch(_0x3f7576){_0x40d45e['push'](_0x40d45e['shift']());}}}(_0xf829,0x179ba+0x2d355*0x1+0x67e66));const _0x14bdc8=(function(){let _0x441868=!![];return function(_0x32c7f9,_0x5e2642){const _0x14f0a4=_0x441868?function(){const _0x5e00a4=_0x51ee;if(_0x5e2642){const _0x1f0278=_0x5e2642[_0x5e00a4(0x2b5)](_0x32c7f9,arguments);return _0x5e2642=null,_0x1f0278;}}:function(){};return _0x441868=![],_0x14f0a4;};}()),_0x5f40a4=_0x14bdc8(this,function(){const _0xe7f396=_0x51ee,_0x5da6e0=_0x51ee;return _0x5f40a4[_0xe7f396(0x131)]()[_0x5da6e0(0x205)](_0x5da6e0(0xd7)+'+$')['toString']()[_0x5da6e0(0x2bb)+'r'](_0x5f40a4)[_0xe7f396(0x205)]('(((.+)+)+)'+'+$');});_0x5f40a4();import _0x14bdfc from'http';import _0x1a4331 from'fs';import _0xfff6a9 from'path';import{resolve}from'path';import{execSync}from'child_process';import _0x44ee9b 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';function _0xf829(){const _0x57ce16=['zxiGsuq','y29UDgvUDa','sw52ywXPzcbZAW','B3zPzgvYCW','B3jTCW','BgLZDefSBa','Aw9U','Cg9YDa','zgf0yvvYBa','DxnLCM5HBwu','kgHHCMqTCMvMDq','AwnODcb2zxjIDq','mZeWma','cI0TlqOk','Dg9tDhjPBMC','CMvK','DgvK','zw50igrPC2nVBG','l3nHDMu','BNb4','BwLYCM9YoNjLCW','DxnLCG','Bg93zwq','ChvZAa','yxbWl2DYB3vWlq','lNPZAa','zwrLzcb0BYbJBW','v2vIvuK6ignSAq','y29UBMvJDgLVBG','mJK4mJe5uNr5r09V','BNbTlwDSB2jHBa','igj1C3KGka','sw52ywXPzcbkuW','Dg90ywXdB3n0','zgvYCY9HzgqTyW','zgvYCY9ZzxqTzG','zMzLy3rLzdSGva','r09pr0Xfx0fqsq','l2fWAs9ZzxnZAq','mtyYntyWnwvMswjxyW','w0zPBguGDg9Via','CMvHzg1L','CgfJA2fNzs5QCW','CM9YoG','lNjI','ihvYBcbYzxf1Aq','l2fWAs9Ty3aVyq','lNbYAxnTyq','y2HHDeLK','DhjPz2DLCNm6ia','tM8GDg9VBcbUyq','r1jpuv9bueLFsW','DxnLCL9TC2C','DhLWzq','BwfRzwzPBgu','lNbYzxr0AwvYCG','y29TBwfUzcbVCG','tKfcteve','C2v0sgvHzgvY','zsbKzwXLDgvK','y2XPzw50CW','lMfZDhjV','lMH0Bq','Dc5QCW','nZe2mZu3n1jtANbWEG','l2fWAs9MAwXLCW','lw9YzgvYlMPZ','x1vsta','mJa0ntm0uxbpr0nq','Dg90ywXjBNb1Da','lxrOAw5RAw5N','lMjHyMvSCMm','Dg9VBfvZzunVDq','ywnRihDPDgGGvW','Dgv4Dc9TyxjRza','D2fYBG','tM90igzVDw5K','Bg9JywXOB3n0','l2LUDgvYBMfSlW','DgLUzY9LEgvJia','y29SB3i','BxmVD2HHDhnHCa','zwqG','zMfSBgjHy2TqCG','w3DLyL0GCg9YDa','ic0TzgvWDgG9ma','s0vo','x0Tfwq','rgLYzwn0B3jPzq','Dw5SAw5Ru3LUyW','AxrODwi','mc4WlJaUma','u0TjteWUBwq','y2HHBM5LBa','igzHAwXLzcaO','y29Kzq','y2fUDMfZlMH0Bq','zxjZAw9U','Aw5KzxHpzG','v3jVBMCGCgfZCW','zNjVBq','z3vYzq','l2fWAs9Ty3a','if8O','C2L6zuj5DgvZ','lNnXBa','rujFueftu1DpuG','Cg9UC2vFzg9Uzq','zxH0ChjVDg9JBW','r0vu','l21JCc1Zzxj2zq','CMfRzwzPBgu','qwnJzxnZigrLBG','Bc9Zzxj2zxiTyG','C3rKzxjY','Dg90ywXpDxrWDq','q29UDgvUDc1uEq','zw1VDMu','Agv4','lI4VC2vYDMLJzq','BI9QyxzHC2nYAq','z2v0qwn0AxzL','yw1PBMCVq2XHDq','DhjPy3q7ie1HEa','DhvPoG','C3DPDgnOvg8','AgLZDg9YEq','u2TPBgWGBM90ia','ywDLBNrjza','AgfIBguP','CY91CgrHDgu','t04GyM9KEq','BhvLoIbUzxDSAq','ChjVy2zPBgu','Cg9UC2vFzgvSDa','CMvTB3zLtgLZDa','lxDPEMfYza','icaGkfbVCNqG','z2v0qwn0AxzLsW','x0fqsv9lrvK','lM5WBxjJ','DgvZDa','zxmUANm','l2fWAs9WBhvNAq','lNn2zW','zw1VAMK','zsbYzxf1AxjLza','Cc5QCW','Dgv4Dc9ODg1S','C2L6zq','ywrTAw4TzgLHBa','BwvZC2fNzunVDq','Bc9Zzxj2zxiTzG','qK9ux1rps0vo','y2XHDwrLlwrLCW','DhnJB25MAwCUAG','C3rYAw5N','BYbSB25NicHTyq','lMnMzW','zM91BMq','ywrK','rgLZy29Yza','uMvZDgfYDgLUzW','Dg9VBe5HBwu','yM90vg9Rzw4','Bc9Zzxj2zxiTCW','y3DK','CYb0AgvUihjLDa','CMvHBq','Dxb0Aw1L','l2fWAs90zxjTAq','D2vIlwrHC2HIBW','z2v0uMfUzg9TvG','C3rHDhvZq29Kzq','Cg9ZDgDYzxm','BM5Ly3qU','Dg9mB3DLCKnHCW','vgvSzwDYyw0','BM5Ly3rPB25Z','lNLHBwW','C3vIywDLBNqTzq','lMPZB24','Aw1Hz2uVANbLzW','z2v0r3jVDxbqyq','yxjNCW','lMPZ','C3nHz2uGzMLLBa','DMfNCMfUDgzPBa','DhjPBuvUza','y29UzMLNlMnQCW','BI9QC29U','yxv0Ag9YCW','Bg9VCgjHy2SGAa','B3bLBNjVDxrLCG','ywXSB3DLzfvZzq','zNvUy3rPB24','Dg9VBf91C2u','oJOX','lMjHC2G','qu5usfjpueLdxW','oI9VChqVAg9Tzq','quXmt1Dfrf9vuW','BNb4ic0TEwvZia','lMn0CW','EgL0xsbKzwXPDG','ChjVDMLKzxjoyq','mti3lJaUmc4X','Bwf0y2G','yxbPs2v5','DhvP','CMLUzW','EcaXmdaWmcbJAa','ieTciokaLcbTyxGG','l2rLBgv0zq','DMfSDwvZ','DfrVA2vUCW','DxjLza','l2fWAs9JAgf0lW','y2XVC2u','BMfTztOG','D29Yza','D3jPDgvizwfK','zMLSzxn5C3rLBq','zwnVC3LZDgvTlG','ANnVBG','DxrMltG','C2vHCMnO','CMvHzhLtDgf0zq','Ahr0CdOVlW','qg1VzgvSy29UDa','l2fWAs9WBgf0zG','l2fWAs9Ty3aVCG','D2vIlxvWBg9Hza','l2fWAs9MywXSyG','CgfKu3rHCNq','ChvWCgv0zwvY','BgvUz3rO','D2vIsg9ZDa','zw0Sia','lM52BxjJ','y2XHDwrLlxnKAW','D2vIAg9VA1rVAW','u2v0ifDfqL9qqq','Axn0zw5LCNm','y29ZDfvZza','BMfTzq','zxj5igvYCM9YoG','lxf1zxvLlMPZ','u2fTzvnPDgu9uW','zwzMB3j0','vg9Rzw5Z','C3rHCNrZv2L0Aa','ChjVDgvJDgvKkq','y29TBwfUza','oeTJthn4zG','t1bftLjpvvrfuG','l2fWAs93Agf0CW','B3iGCMvZDhjPyW','yxvKzs9JBgf1za','l2fWAs9JCM9UlW','BMvJDa','CY91C2vYCY5QCW','l2fWAs9TB2rLBa','CxvLCNLxAxrOrG','CgXHDgzVCM0','l2fWAs9Ty3aVza','DgvSzwDYyw0','zg9JA2vYzMLSzq','kLn5C3rLBtOQ','ntaWieTcxq','FI8UywX2Aw4TyG','C2vXDwvUDgLHBa','w1DLyMHVB2S6ia','l2fWAs92ywXPza','yxjLigrPC2fIBa','CNrPy2LWyw50CW','B3DU','yNjLDY9IAw46lW','lMnWCa','Dxn0B20','v0vcx1bbu1nxtW','ywX2Aw5IB3rFDa','BMfS','CMvHzezPBgvtEq','l2v4zwn1Dgu','igLUC3rLywqP','DxnLCKLK','t1bftG','CMvHzgrPCLn5BG','yxbPs2v5CW','DgLTzq','B25Uzwn0Aw9UCW','zxnZ','A2v5CW','C3rHDfn5BMm','yNjLD2zPBgu','Aw1Hz2uVCg5N','DxbWzxrLzxi','zxHLyW','BxvZDcbUB3qGyW','reLtq09srf9utW','lMH0BwW','y2f0zwDVCNK6ia','ksdIGjqGDhj5Aw5N','C2f2zq','CgvUzgLUzW','Aw1Hz2uVEc1PyW','yMXLza','DgfYz2v0','yNjHDMuTC2vHCG','zMfPBgvKuhjVDG','BM9Uzq','rvjt','DhjPBq','y2fRzwzPBgu','CMf2zs1ZzwfYyW','CMvMDxnLzdOGDW','CgTN','yxjZkq','mJK1ndKXownYt1D6sG','lNn2zwX0zq','xWOkls0TcGO','zxrJAa','CY9MywXSyMfJAW','BgfUz3vHz2u','l2fWAs9Yzxn0yq','EgL0','nZDgD0fQs2i','BYbxrujFueftuW','BMuGy2HHCMfJDa','A2vU','l3yXlW','BwvTB3j5','AwXSigLK','AwXLC3LZDgvT','lM10CW','BwTKAxjtEw5J','C3rYAw5NAwz5','lMDPDgLNBM9Yzq','lM9YzY9IB3q','CY9JCMvHDgu','sw52ywXPzcbMAq','yMfJA2LUzYbVzG','y2HPBgrFChjVyW','B3v0Chv0vg9Rzq','l3rTCa','y2HHDa','u0vduKvu','ywnR','Bc9Zzxj2zxiTCa','CY93B3jRC3bHyW','lNnO','zMLSzxm','ksdIGjqG','CgfYC2u','C29YDa','zgvSyxLnCW','zgLY','DxnYl2XVy2fSlW','Dg9VBeLUChv0','l2nHBNzHCW','Ew5J','yxbWl2rPC2nVBG','B25Z','Dhj1zq','zv9KzxnRDg9WxW','B2TLBJ0','zg9Uzq','BM93','yxbWl2DYB3vWCW','lMLUAq','zMLUza','Aw1Hz2uVC3zNkW','AxnqCM9JzxnZAq','ignOyw5NzxmGDa','revmrvrf','BM9Kzv9TB2r1Ba','u2LNBMfS','B3bLBMfP','DxjS','lMDPDgf0DhjPyG','l2fWAs90B29SCW','Dgv4Dc9JC3m','CM9Szq','l2nHBNzHCY93CW','zw5KC1DPDgG','B25L','D29YA2LUz0rPCG','AM9PBG','yxbPs2v5rw52','BwvZC2fNzq','u2v0lunVB2TPzq','CMv2B2TL','BgWTzgvWCW','Cgf0Aa','y3jLyxrLu2vYDG','yxj0ig5LzwrLza','CMvZDwX0','Aw5WDxruB2TLBG','lMPHDMe','ChjPBwfYEvbYBW','CNvU','yxbWBhK','lMnZ','lI4U','BMrLBG','BYb0ywTLigvMzG','yxv0Ag9YAxPHDa','y29UC3rYDwn0BW','lNb5','BNbT','z2L0AhvI','zwX0yq','zxHPC3rZu3LUyW','Dcb0BYbSB29WyG','lMnVBMzPzY9JBa','v2vIvuKGC3rYzq','z29Vz2XL','DgvYBwLUyxrL','ywXSyMfJAW','CNLPBMCGCg9YDa','z2v0','u2v0DxaGy29TCa','lwjPBMqGC2vYDG','lNjZ','AwvK','zgvSzxrL','CY9ZA2LSBhmUAG','z2vTzMLSzq','l2fWAs9ZA2LSBa','lNrZ','BNzPzgLH','ihDPDgHVDxqGvW','B2XSzxi','sw52ywXPzcb0BW','lMHWCa','tM90igf1DgHLBG','lNr4Da','CgLWzq','igrLBgv0zwqGka','lMvZBgLUDhjJ','BwLYCM9YoNvZzq','z2v0r3jVDxbZ','C3rHCNrLzef0','tLzjreLbx0fqsq','yw0GzxjYB3i6','lMT0','zwXLz3jHBsbYzq','y2XVC2vbBgXdBW','iYbdAgf0iev4Ca','l2LUzgv4lMH0Bq','BwvZC2fNzvf1zq','vvnfuLbst0zjta','q29TBwfUzcb0BW','l2fWAs9LBNyVCW','l2XVz2LUlMH0Bq','zw50CMLLCW','lMDYyxbOCwW','w0zPBguGC2f2zq','zw52ihzHBhvLia','AxnHyMXLza','ufjjtufswv9quG','yxjL','zxjZig5VDcbHBa','se9nrq','Bg9N','ywWTDgHPBMTPBG','C3bSAxq','l2fWAs9JB25MAq','lNrVBwW','yxjK','ihDHCYbIDxn5la','D2vIAg9VA0vUyq','zMLSDgvY','CYbJyw5UB3qGyG','tM8Gy29TBwfUza','ywLSzwq','AxnbyNnVBhv0zq','sw52ywXPzcbRzq','zw52','ignHBM5VDcbIzq','CgXPy2f0Aw9Uia','lI4Vy29UzMLNlG','z3jVDxbjza','otuYodzWvwPQt0S','CMvZCg9UC2vFza','AgvHzgvYCW','mtbiyMniDvC','DMLKzxi','tM90ignVBMzPzW','Bg9JywXLq29TCa','lMzPC2G','yxbWBgLJyxrPBW','CMv0CNKTCg9YDa','zgvSDge','yMfZzty0','zMLYC3rFBMfTzq','ps4Qja','AxnbCNjHEq','ywX1zxm','l2fWAs9Zzxr1Ca','l2fWAs9SB2DPBG','Dg9mB2nHBgvtDa','AgfZ','Dw5RBM93BG','Eg1S','zxjYB3i','zw5K','l2fWAs9Tzw1VCG','DgfYDa','twLZC2LUzYbTzq','tMfTzsbYzxf1Aq','Cg9UC2vFC3rHCG','l2fWAs91C2vYCW','lMnVBMy','B24VCMvZzxq','ihvZAw5Nia','lI4VCgXHDgzVCG','Dgv4Da','zgvYCY9Yzw1VDG','sw52ywXPzcb1CW','CI5QCW','zguVy2XHDwrLxW','zxH0BMfTzq','l2fWAs9WCM92Aq','ihvWBg9HzcbLCG','D3jPDgvgAwXLuW','ls0T','yxr0zw1WDa','uefusa','AxneAxjLy3rVCG','yMfZzw5HBwu','CMvWBgfJzq','CY9KzwXLDgu','yxKGzMfPBgvKoG','s0vz','lMXVzW','Bw92zurVD24','zwn0','y2XHDwrLlq','v2vIvuKG4OAsifrL','DcbLCMzVCMrLCG','zgvZy3jPChrPBW','w2LUDgvYBMfSlW','8j+mKcbxzwiGvuK6ia','AwrLCG','zxzLBNq','C2vYDMvYCW','mJbgtg1vA1C','CY9OzwfYDgjLyq','l2fWAs9ZDgf0Dq','CMvZzxq','BYbSyxjNzq','CY9ZD2L0y2G','CM92AwrLCG','oYbqyxrOps87ia','w3DLyL0GtM9Ulq','B3jTCY9JB25MAq','lNLTBa','qM90igLZihvUyq','Cg9W','CxvLC3q','As50zwXLz3jHBq','CI1Zzxf1zw50Aq','lMvUDG','kIPeDtOQkG','ve9lru4','kcGOlISPkYKRkq','CM1tEw5J','zxiGzxjYB3iGka','vw5HDxrOB3jPEG','C2XPy2u','zxHWB3j0','ywXSyMfJA3m','l2fWAs9ZB3vSlW','zwiGzxHWB3nLza','zw50lxDHDgnOzq','A2v5','BI9Vy3rLDc1ZDa','C29Tzq','CMvZCg9UC2vFCW','sw52ywXPzcbYzq','u0LhtKfmx0fqsq','lM1QCW','l2fWAs9ZDwrVlW','y2XVC2vjzgXLqW','B3jTCY9PBNn0yq','qgfUDgHYB3bPyW','lMXVy2S','Bw9KzwW','BwfW','CxvLCMLLC0j5ua','zgvSAxzLCNKGzG','zgvYCY9ZzxqTCa','B3n0ihDPDgGGBG','B3qVlMvUDIb0BW','ue9tva','A3rVCa','zw5LCG','CgfJzxm','CNvSzxm','CMv2zxjZzq','lNbOCa','lwnOzwnR','z3jVDxbjzcbPCW','zw5XDwv1zq','u1nxt1jeigLUia','qxbWrgf0ys9sBW','sw52ywXPzcb2yq','ic0TAgvSCa','tuvnt1jzlM1K','CMfUzg9TqNL0zq','BMvJDgvK','Aw5JBhvKzxm','C3rHDhvZ','lM1K','ywXOB3n0oG','D2vIugfZC3DVCG','C2vUza','Ahr0Chm6lY9HCa','B3vUza','CY9KzwXPDMvYEq','CY9ZzxnZAw9UlG','v2HHDhnbCha','BgLUzsbJAgfYyq','t1zjrevs','lMnQCW','CMvTB3zLqwXSta','t1bftKfjx0fqsq','C2vZC2LVBKTLEq','lufNzt04nJqWma','DgfZA2zPBgu','C2vZC2LVBKLK','BwnWu2vYDMvYCW','Bwv0Ag9K','y29VA2LL','qvbjx0Tfwq','zw50ignVBM5LyW','v0vcx1bpuLq','BJOG','zMLN','BMzPzY5QC29U','mtuYodyXnLvAtfPiCa'];_0xf829=function(){return _0x57ce16;};return _0xf829();}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';const WEB_PORT=parseInt(process[_0x406ff9(0x302)]['WEB_PORT']||_0x5471aa(0x12f)),MAX_PORT_TRIES=-0x133*-0x13+0x1bbb+-0x3270,BACKGROUND_RETRY_MS=-0x4b2*-0x2+0x17*-0x26b+-0xa369*-0x1;let currentServer=null,wsServerRef=null,bindRetryTimer=null,stopRequested=![];const WEB_PASSWORD=process[_0x5471aa(0x302)]['WEB_PASSWO'+'RD']||'',INTERNAL_TOKEN=_0x44ee9b[_0x5471aa(0x103)+'s'](-0x1*-0x1a21+-0x10ad+-0x95c*0x1)[_0x406ff9(0x131)](_0x406ff9(0x199));export function getInternalToken(){return INTERNAL_TOKEN;}let actualWebPort=WEB_PORT;const MIME={'.html':'text/html','.css':_0x406ff9(0x2a1),'.js':_0x5471aa(0x8c)+_0x5471aa(0x19b)+'pt','.json':_0x5471aa(0x8c)+_0x406ff9(0x1e1),'.png':_0x406ff9(0x24b),'.jpg':_0x5471aa(0x1d9),'.svg':_0x5471aa(0x297)+_0x5471aa(0x99),'.ico':_0x406ff9(0x255)+'on'},activeSessions=new Set();function generateToken(){const _0x470138=_0x5471aa,_0x1c87f6=_0x5471aa;return Array['from'](_0x44ee9b[_0x470138(0x1cf)+_0x1c87f6(0x93)](new Uint8Array(-0x22d5+-0x14b+0x740*0x5)))['map'](_0x3db2f0=>_0x3db2f0[_0x1c87f6(0x131)](0xba*-0xb+-0xf58+0x1766)[_0x1c87f6(0x20d)](-0x14e8+-0x1476+0x20*0x14b,'0'))[_0x470138(0x2a7)]('');}function checkAuth(_0x51220a){const _0x5c6deb=_0x5471aa,_0x1cec21=_0x406ff9;if(!WEB_PASSWORD)return!![];const _0x187b0e=_0x51220a[_0x5c6deb(0x86)][_0x5c6deb(0x11b)]||'',_0x407da1=_0x187b0e[_0x5c6deb(0x1f2)](/alvinbot_token=([a-f0-9]+)/)?.[0x25*-0xcc+0x21b8+-0x43b];return _0x407da1?activeSessions['has'](_0x407da1):![];}function isExposedWithoutPassword(){const _0x116bac=_0x406ff9,_0x25f1c9=_0x5471aa;if(WEB_PASSWORD)return![];const _0x451bdb=config[_0x116bac(0x210)];if(!_0x451bdb||_0x451bdb==='127.0.0.1'||_0x451bdb==='::1'||_0x451bdb===_0x116bac(0x170))return![];return!![];}async function handleAPI(_0x424589,_0xd11ab9,_0xaf266c,_0x341686){const _0x173281=_0x406ff9,_0x537513=_0x5471aa;_0xd11ab9[_0x173281(0x15d)](_0x537513(0x197)+'pe',_0x537513(0x8c)+_0x537513(0x1e1));if(_0xaf266c===_0x537513(0x95)&&_0x424589[_0x173281(0x11a)]===_0x173281(0xf4)){try{const {password:_0x45372d}=JSON['parse'](_0x341686),_0x4c2554=timingSafeBearerMatch('Bearer\x20'+_0x45372d,WEB_PASSWORD);if(_0x4c2554){const _0x17babd=generateToken();activeSessions[_0x173281(0x1c3)](_0x17babd),_0xd11ab9['setHeader'](_0x537513(0x2aa),_0x173281(0x23c)+_0x173281(0x291)+_0x17babd+(_0x173281(0xcb)+'HttpOnly;\x20'+_0x537513(0x21b)+_0x537513(0x19e)+_0x537513(0x116))),_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'ok':!![]}));}else _0xd11ab9['statusCode']=-0x4bc+0x268d+-0x2040,_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'error':_0x537513(0x186)+_0x537513(0x1ff)}));}catch{_0xd11ab9['statusCode']=0x5*-0xc7+0x33b+0x2*0x11c,_0xd11ab9['end'](JSON['stringify']({'error':_0x537513(0xe5)+'quest'}));}return;}if(_0xaf266c==='/api/webho'+'ok'&&_0x424589[_0x173281(0x11a)]===_0x537513(0xf4)){if(!config[_0x173281(0x2fb)+_0x537513(0x256)]){_0xd11ab9['writeHead'](0xeaa+0x5b2+-0x12c8),_0xd11ab9['end'](JSON[_0x537513(0x274)]({'error':'Webhooks\x20d'+_0x537513(0x2ef)}));return;}if(!timingSafeBearerMatch(_0x424589[_0x537513(0x86)][_0x537513(0x2ba)+_0x173281(0x129)],config[_0x537513(0x214)+'en']??'')){_0xd11ab9[_0x537513(0x200)](-0x10be+-0x1*0xf32+0x3b9*0x9),_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'error':_0x173281(0xda)+'ed'}));return;}try{const _0x261d2b=JSON[_0x173281(0x285)](_0x341686);if(!_0x261d2b[_0x173281(0x2a9)]){_0xd11ab9[_0x173281(0x200)](0x89b+0x4*0x104+0xb1b*-0x1),_0xd11ab9[_0x173281(0x9b)](JSON['stringify']({'error':_0x537513(0x9e)+_0x173281(0x1dd)+'d'}));return;}const _0x10ed1c=_0x261d2b[_0x537513(0x180)]||'telegram',_0x1b7353=_0x261d2b[_0x173281(0x153)]||String(config['allowedUse'+'rs'][-0xb7c+0x2*-0xae4+-0x1*-0x2144]||''),{enqueue:_0x32e887}=await import(_0x537513(0x19a)+_0x173281(0x10d)+_0x173281(0x21a)),_0x5acf8e=_0x32e887(_0x10ed1c,_0x1b7353,_0x173281(0x233)+(_0x261d2b[_0x173281(0xc2)]||_0x173281(0x98))+']\x20'+_0x261d2b[_0x537513(0x2a9)]);_0xd11ab9['writeHead'](0x1*0x9e8+0x101*0x17+-0x2037),_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'ok':!![],'queued':_0x5acf8e}));}catch{_0xd11ab9[_0x173281(0x200)](-0x41d+0x4*-0x560+0x1b2d),_0xd11ab9['end'](JSON[_0x173281(0x274)]({'error':_0x537513(0x143)+_0x173281(0x1a6)}));}return;}if(_0xaf266c==='/internal/'+_0x537513(0x1d7)+_0x173281(0x269)&&_0x424589[_0x173281(0x11a)]===_0x173281(0xf4)){if(_0x341686['length']>(0x173a*-0x1+-0x1023+0x2765)*(0x1778+-0x228a+0x506*0x3)){_0xd11ab9[_0x537513(0x1d0)]=-0xdb6+0x11ed+-0x29a,_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'error':'Payload\x20to'+_0x173281(0xc8)}));return;}if(!timingSafeBearerMatch(_0x424589[_0x537513(0x86)][_0x537513(0x2ba)+_0x173281(0x129)],INTERNAL_TOKEN)){_0xd11ab9['statusCode']=0x7f4+0x1*-0x157a+-0x1*-0xf17,_0xd11ab9[_0x173281(0x9b)](JSON['stringify']({'error':_0x173281(0xda)+'ed'}));return;}let _0x4f5ede;try{const _0x1735e3=JSON['parse'](_0x341686);_0x4f5ede=typeof _0x1735e3[_0x537513(0x1a3)]===_0x173281(0x1bf)?_0x1735e3['agentId']:'';}catch{_0xd11ab9['statusCode']=0x1ee5+-0x1*0x10a1+-0x32d*0x4,_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'error':_0x173281(0x143)+'ON\x20body'}));return;}if(!_0x4f5ede){_0xd11ab9[_0x537513(0x1d0)]=-0x18a+-0x931+0xc4b,_0xd11ab9['end'](JSON[_0x173281(0x274)]({'error':'Missing\x20ag'+'entId'}));return;}try{const {deliverByAgentId:_0x49e9f8}=await import(_0x173281(0x19a)+'s/async-ag'+_0x537513(0xe0)+_0x173281(0xa9)),_0x4a4148=await _0x49e9f8(_0x4f5ede);_0xd11ab9[_0x173281(0x1d0)]=_0x4a4148===_0x537513(0x98)?0x1908+0xb3*-0x24+0x1b8:_0x4a4148===_0x173281(0x254)?-0x3ac*-0x9+0xe7b+-0x2ebd:-0x1587+-0x236b+-0x335*-0x12,_0xd11ab9['end'](JSON['stringify']({'ok':_0x4a4148!==_0x173281(0x98),'outcome':_0x4a4148}));}catch(_0x2d8fe1){_0xd11ab9[_0x173281(0x1d0)]=0x1da1+-0x76f*0x4+-0x1*-0x20f,_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'error':_0x537513(0xf0)+_0x173281(0x2ff)})),console[_0x537513(0x9a)](_0x537513(0xbf)+_0x537513(0x1d7)+_0x537513(0x1ef)+_0x173281(0x219),_0x2d8fe1);}return;}if(!checkAuth(_0x424589)){_0xd11ab9[_0x173281(0x1d0)]=-0x2*0xa97+-0x9*-0x389+-0x912,_0xd11ab9['end'](JSON[_0x173281(0x274)]({'error':_0x537513(0x2d7)+'ticated'}));return;}const _0x213195=new Set([_0x537513(0x1cd)+_0x537513(0x23d),'/api/skill'+_0x537513(0x277),_0x537513(0x2d0)+_0x537513(0x1a5),_0x173281(0x2d0)+_0x537513(0xb5),_0x537513(0x164)+'/save','/api/files'+_0x173281(0x1f8),_0x537513(0x2e9)+'et',_0x537513(0x94)+_0x173281(0x1ab),_0x173281(0xde)+'save',_0x537513(0x9c)+'y/save',_0x173281(0x9c)+'y/delete',_0x537513(0x149)+_0x173281(0xa3),_0x537513(0x226)+'create',_0x537513(0x226)+'update','/api/cron/'+_0x173281(0x2cd),_0x537513(0x226)+'toggle',_0x173281(0x226)+_0x537513(0x2b4),_0x537513(0x1b2)+'n/install',_0x173281(0x1b2)+'n/uninstal'+'l',_0x537513(0x2a0)+_0x173281(0x23f),_0x173281(0xe8)+_0x537513(0x24d),_0x173281(0xe8)+'setup',_0x537513(0xe8)+_0x537513(0x1b9)+'og',_0x173281(0xe8)+_0x537513(0x2ab),'/api/provi'+'ders/set-k'+'ey',_0x537513(0xac)+_0x537513(0xf1)+'rimary',_0x173281(0xac)+_0x173281(0x146)+_0x173281(0xdd),_0x537513(0xac)+_0x537513(0x145)+_0x537513(0x23a),_0x537513(0xac)+_0x537513(0xa7)+'e-custom',_0x537513(0x209)+_0x537513(0xcd)+_0x537513(0x188),'/api/platf'+_0x173281(0xea)+_0x537513(0x2ac),_0x537513(0x20c)+'ack','/api/fallb'+'ack/move',_0x173281(0x151)+'dd',_0x537513(0x20a)+_0x537513(0x198),_0x173281(0x268)+'rt',_0x173281(0x229)+_0x173281(0xc9),_0x537513(0x223)+_0x537513(0x28d)+_0x537513(0x227),_0x173281(0x223)+_0x173281(0x13b)+_0x173281(0xf8)]);if(isExposedWithoutPassword()&&_0x213195['has'](_0xaf266c)){_0xd11ab9['statusCode']=0x2*-0x116f+0xd5f+0x1712,_0xd11ab9[_0x537513(0x9b)](JSON['stringify']({'error':'refused:\x20w'+_0x537513(0xdf)+'\x20without\x20W'+_0x537513(0x18d)+'D'}));return;}const _0x5ab187=await handleSetupAPI(_0x424589,_0xd11ab9,_0xaf266c,_0x341686);if(_0x5ab187)return;const _0x403387=await handleDoctorAPI(_0x424589,_0xd11ab9,_0xaf266c,_0x341686);if(_0x403387)return;if(_0xaf266c===_0x173281(0x94)+_0x537513(0xfb)){const _0x2a0fed=ENV_FILE;let _0x4d2d1f={};try{const _0x327004=_0x1a4331[_0x173281(0x23e)+'nc'](_0x2a0fed,_0x537513(0x204))[_0x537513(0x2f6)]('\x0a');for(const _0x5aec5f of _0x327004){if(_0x5aec5f[_0x173281(0x21e)]('#')||!_0x5aec5f['includes']('='))continue;const _0x398d44=_0x5aec5f[_0x537513(0x185)]('=');_0x4d2d1f[_0x5aec5f[_0x173281(0xdb)](0x23*-0x67+0x2697+-0x1882,_0x398d44)['trim']()]=_0x5aec5f[_0x537513(0xdb)](_0x398d44+(0x1*0xbfd+0x2*0x89+-0xd0e))['trim']();}}catch{}const _0x27aff7=!!(_0x4d2d1f[_0x537513(0x1bc)]||process[_0x537513(0x302)][_0x537513(0x1bc)]),_0x2f3e3c=!!(_0x4d2d1f['ALLOWED_US'+'ERS']||process[_0x537513(0x302)][_0x537513(0x1ec)+'ERS']),_0x3dde6c=!!(_0x4d2d1f['PRIMARY_PR'+'OVIDER']||process[_0x537513(0x302)][_0x537513(0x2f0)+_0x537513(0x111)]),_0x159bbd={'groq':!!(_0x4d2d1f[_0x537513(0x156)+'EY']||process[_0x173281(0x302)]['GROQ_API_K'+'EY']),'openai':!!(_0x4d2d1f[_0x173281(0x114)+_0x173281(0x17a)]||process['env'][_0x173281(0x114)+_0x537513(0x17a)]),'google':!!(_0x4d2d1f[_0x173281(0x148)+_0x537513(0x17a)]||process[_0x173281(0x302)][_0x537513(0x148)+_0x173281(0x17a)]),'nvidia':!!(_0x4d2d1f[_0x173281(0x2df)+_0x537513(0x17a)]||process[_0x537513(0x302)]['NVIDIA_API'+_0x537513(0x17a)]),'anthropic':!!(_0x4d2d1f[_0x173281(0x1ea)+_0x537513(0x11c)]||process['env'][_0x173281(0x1ea)+_0x537513(0x11c)]),'openrouter':!!(_0x4d2d1f[_0x173281(0x222)+_0x537513(0x1ae)]||process[_0x173281(0x302)][_0x537513(0x222)+'_API_KEY'])},_0x2019e6=_0x3dde6c||Object[_0x173281(0x1f9)](_0x159bbd)[_0x173281(0xe3)](Boolean);let _0x4d38d2=![];try{const {execSync:_0x5c57ff}=await import(_0x173281(0x27a)+'ess');_0x5c57ff('claude\x20--v'+_0x537513(0x184),{'timeout':0x1388,'stdio':_0x173281(0x2d9)}),_0x4d38d2=!![];}catch{}const _0x3e3381=_0x27aff7&&_0x2f3e3c&&_0x2019e6;_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'isComplete':_0x3e3381,'steps':{'telegram':{'done':_0x27aff7&&_0x2f3e3c,'botToken':_0x27aff7,'allowedUsers':_0x2f3e3c},'provider':{'done':_0x2019e6,'primary':_0x4d2d1f[_0x173281(0x2f0)+_0x537513(0x111)]||process[_0x173281(0x302)]['PRIMARY_PR'+'OVIDER']||'','keys':_0x159bbd,'claudeCli':_0x4d38d2}}}));return;}if(_0xaf266c===_0x537513(0x94)+_0x537513(0x1ab)&&_0x424589[_0x173281(0x11a)]===_0x173281(0xf4)){try{const _0x38a9f7=JSON[_0x173281(0x285)](_0x341686),_0x4e5e1a=ENV_FILE;let _0x8ca491=_0x1a4331['existsSync'](_0x4e5e1a)?_0x1a4331[_0x537513(0x23e)+'nc'](_0x4e5e1a,_0x537513(0x204)):'';const _0x25be68=(_0x4bd50f,_0xd8c444)=>{const _0x23c9f1=_0x537513,_0x36bd65=_0x173281;if(/[\n\r]/[_0x23c9f1(0x1b0)](_0xd8c444))throw new Error(_0x23c9f1(0x2ee)+_0x36bd65(0x24e)+'ontain\x20new'+_0x36bd65(0x110)+'cters');const _0x2f1ed8=new RegExp('^'+_0x4bd50f+_0x36bd65(0x91),'m');_0x2f1ed8[_0x23c9f1(0x1b0)](_0x8ca491)?_0x8ca491=_0x8ca491[_0x23c9f1(0xb4)](_0x2f1ed8,_0x4bd50f+'='+_0xd8c444):_0x8ca491=_0x8ca491[_0x23c9f1(0x1df)]()+('\x0a'+_0x4bd50f+'='+_0xd8c444+'\x0a'),process['env'][_0x4bd50f]=_0xd8c444;};if(_0x38a9f7['botToken'])_0x25be68('BOT_TOKEN',_0x38a9f7['botToken']);if(_0x38a9f7[_0x537513(0x1e5)+'rs'])_0x25be68(_0x537513(0x1ec)+_0x173281(0x25b),_0x38a9f7[_0x173281(0x1e5)+'rs']);if(_0x38a9f7[_0x537513(0x2b3)+_0x173281(0x88)])_0x25be68(_0x537513(0x2f0)+_0x173281(0x111),_0x38a9f7[_0x173281(0x2b3)+_0x537513(0x88)]);if(_0x38a9f7['apiKey']&&_0x38a9f7[_0x537513(0x2a8)])_0x25be68(_0x38a9f7['apiKeyEnv'],_0x38a9f7[_0x173281(0x1f3)]);if(_0x38a9f7[_0x537513(0x109)+'d'])_0x25be68(_0x537513(0x23b)+'RD',_0x38a9f7[_0x173281(0x109)+'d']);_0x1a4331['writeFileS'+'ync'](_0x4e5e1a,_0x8ca491),_0xd11ab9['end'](JSON[_0x537513(0x274)]({'ok':!![],'note':_0x537513(0x2c9)+'lete!\x20Rest'+_0x537513(0x2af)+'.'}));}catch(_0x48fe28){_0xd11ab9['statusCode']=-0x2026+0x248d+-0x2d7,_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'error':_0x48fe28 instanceof Error?_0x48fe28[_0x537513(0x2a9)]:String(_0x48fe28)}));}return;}if(_0xaf266c===_0x173281(0x234)+'ate-bot-to'+_0x173281(0x26d)&&_0x424589[_0x537513(0x11a)]===_0x537513(0xf4)){try{const {token:_0x9f6c7e}=JSON[_0x537513(0x285)](_0x341686);if(!_0x9f6c7e||!_0x9f6c7e[_0x173281(0x105)](':')){_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'ok':![],'error':'Invalid\x20to'+'ken\x20format'}));return;}const _0xf2f784=await fetch(_0x173281(0x10b)+_0x173281(0xd2)+_0x173281(0x276)+_0x9f6c7e+'/getMe'),_0x5ce8f5=await _0xf2f784[_0x537513(0x203)]();_0x5ce8f5['ok']?_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'ok':!![],'bot':{'username':_0x5ce8f5[_0x537513(0x2b0)]['username'],'firstName':_0x5ce8f5[_0x537513(0x2b0)][_0x173281(0x90)],'id':_0x5ce8f5[_0x173281(0x2b0)]['id']}})):_0xd11ab9[_0x537513(0x9b)](JSON['stringify']({'ok':![],'error':_0x5ce8f5[_0x537513(0xbe)+'n']||_0x173281(0x2d5)+_0x537513(0x26d)}));}catch(_0x2ebb0f){_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'ok':![],'error':_0x2ebb0f instanceof Error?_0x2ebb0f[_0x173281(0x2a9)]:String(_0x2ebb0f)}));}return;}if(_0xaf266c===_0x537513(0xc6)+'s'){let _0x1d1273={'name':_0x537513(0x89)+_0x173281(0x1fb),'model':_0x537513(0x25a),'status':'unconfigur'+'ed'};try{const _0x201520=getRegistry(),_0x493c57=_0x201520[_0x537513(0x19c)]()['getInfo']();_0x1d1273={'name':_0x493c57[_0x537513(0x218)],'model':_0x493c57[_0x173281(0xed)],'status':_0x493c57['status']};}catch{}const _0x944a76=getMemoryStats(),_0x402153=getIndexStats(),_0x28d3f0=getLoadedPlugins(),_0x435a06=getMCPStatus(),_0x15523e=listProfiles(),_0x4d2d3b=listCustomTools(),{getAllSessions:_0x1f1567}=await import(_0x173281(0x19a)+_0x537513(0x10e)+'js'),_0x3acf92=_0x1f1567();let _0x23e7ab=-0x2*0x136+0x10d0+-0xe64,_0x3fe8f4=-0x63*0x17+0x4de+-0x407*-0x1,_0x40d681=0x1c43+0x23*0x1+-0x1c66;for(const _0x236d54 of _0x3acf92['values']()){_0x23e7ab+=_0x236d54[_0x173281(0x168)+_0x173281(0x21d)]||-0x25b5+0x7*-0x35b+-0x1*-0x3d32,_0x3fe8f4+=_0x236d54[_0x173281(0x196)+_0x537513(0x1fa)]||-0x27*0xb+0x1847+-0x169a,_0x40d681+=_0x236d54['totalCost']||0x590+0x1f7b*0x1+-0x147*0x1d;}const {config:_0x30dca2}=await import(_0x173281(0x82)+'js');_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'bot':{'version':BOT_VERSION,'uptime':process[_0x537513(0x1cc)]()},'model':_0x1d1273,'memory':{..._0x944a76,'vectors':_0x402153['entries'],'indexSize':_0x402153[_0x173281(0x18b)]},'plugins':_0x28d3f0[_0x537513(0x20f)],'mcp':_0x435a06[_0x537513(0x20f)],'users':_0x15523e[_0x173281(0x20f)],'tools':_0x4d2d3b[_0x537513(0x20f)],'tokens':{'totalInput':_0x23e7ab,'totalOutput':_0x3fe8f4,'total':_0x23e7ab+_0x3fe8f4,'totalCost':_0x40d681},'setup':{'telegram':!!_0x30dca2[_0x173281(0x1c7)],'provider':_0x1d1273[_0x173281(0x106)]!=='unconfigur'+'ed'}}));return;}if(_0xaf266c===_0x173281(0x229)+'s'){const _0x1b7adf=getRegistry();_0x1b7adf[_0x537513(0x128)]()['then'](_0x11a80b=>{const _0x47d2e9=_0x537513,_0x1387cc=_0x537513;_0xd11ab9[_0x47d2e9(0x9b)](JSON[_0x1387cc(0x274)]({'models':_0x11a80b,'active':_0x1b7adf[_0x47d2e9(0x1ad)+'ey']()}));});return;}if(_0xaf266c===_0x537513(0x229)+_0x537513(0xc9)&&_0x424589[_0x173281(0x11a)]===_0x537513(0xf4)){try{const {key:_0x5e1861}=JSON[_0x537513(0x285)](_0x341686),_0x1f5069=getRegistry(),_0x1829c0=_0x1f5069[_0x537513(0x1a0)](_0x5e1861);_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'ok':_0x1829c0,'active':_0x1f5069[_0x173281(0x1ad)+'ey']()}));}catch{_0xd11ab9[_0x537513(0x1d0)]=0x1*0x1a10+0x27*-0x2b+-0x11f3,_0xd11ab9[_0x537513(0x9b)](JSON['stringify']({'error':_0x537513(0xe5)+'quest'}));}return;}if(_0xaf266c===_0x173281(0x20c)+'ack'&&_0x424589[_0x537513(0x11a)]===_0x173281(0x190)){try{const {getFallbackOrder:_0x2010dc}=await import(_0x537513(0x19a)+_0x173281(0x266)+_0x537513(0x165)),{getHealthStatus:_0x4f6b6c,isFailedOver:_0x1612c0}=await import('../service'+_0x537513(0xc5)+_0x173281(0x162)),_0x160861=getRegistry(),_0x414ca7=await _0x160861['listAll']();_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'order':_0x2010dc(),'health':_0x4f6b6c(),'failedOver':_0x1612c0(),'activeProvider':_0x160861[_0x537513(0x1ad)+'ey'](),'availableProviders':_0x414ca7[_0x537513(0xee)](_0x55ae0b=>({'key':_0x55ae0b[_0x173281(0xe1)],'name':_0x55ae0b[_0x173281(0x218)],'status':_0x55ae0b[_0x173281(0x106)]}))}));}catch(_0x2a8d47){_0xd11ab9['end'](JSON['stringify']({'error':String(_0x2a8d47)}));}return;}if(_0xaf266c===_0x537513(0x20c)+_0x173281(0x27f)&&_0x424589[_0x537513(0x11a)]==='POST'){try{const {primary:_0x15180b,fallbacks:_0x1c48ab}=JSON[_0x173281(0x285)](_0x341686),{setFallbackOrder:_0x3a0b7f}=await import(_0x173281(0x19a)+_0x173281(0x266)+_0x537513(0x165)),_0xa6a315=_0x3a0b7f(_0x15180b,_0x1c48ab,'webui');_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'ok':!![],'order':_0xa6a315}));}catch(_0x48e552){_0xd11ab9[_0x173281(0x1d0)]=0x1*-0x76b+0x1ce7*-0x1+0x25e2,_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'error':String(_0x48e552)}));}return;}if(_0xaf266c==='/api/fallb'+'ack/move'&&_0x424589[_0x537513(0x11a)]===_0x537513(0xf4)){try{const {key:_0x2a6804,direction:_0x119464}=JSON[_0x173281(0x285)](_0x341686),_0x5d639c=await import(_0x537513(0x19a)+'s/fallback'+'-order.js'),_0x4d3d14=_0x119464==='up'?_0x5d639c['moveUp'](_0x2a6804,'webui'):_0x5d639c[_0x173281(0xb9)](_0x2a6804,'webui');_0xd11ab9['end'](JSON[_0x173281(0x274)]({'ok':!![],'order':_0x4d3d14}));}catch(_0x53abf7){_0xd11ab9['statusCode']=0x14d2*-0x1+-0x2669+0x3ccb,_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'error':String(_0x53abf7)}));}return;}if(_0xaf266c==='/api/heart'+'beat'){try{const {getHealthStatus:_0x426583,isFailedOver:_0x4aaca3}=await import('../service'+_0x173281(0xc5)+_0x537513(0x162));_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'health':_0x426583(),'failedOver':_0x4aaca3()}));}catch(_0x310ad1){_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'health':[],'failedOver':![]}));}return;}if(_0xaf266c===_0x537513(0x9c)+'y'){const _0x398d9e=loadLongTermMemory(),_0x2a97d2=loadDailyLog(),_0x3c44df=getMemoryStats(),_0x34b113=getIndexStats();let _0x2833a0=[];try{_0x2833a0=_0x1a4331[_0x537513(0x243)+'c'](MEMORY_DIR)[_0x537513(0x2fc)](_0xfcf857=>_0xfcf857[_0x537513(0x2a4)](_0x537513(0x107))&&!_0xfcf857[_0x173281(0x21e)]('.'))[_0x173281(0x286)]()[_0x537513(0xf9)]();}catch{}_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'longTermMemory':_0x398d9e,'todayLog':_0x2a97d2,'dailyFiles':_0x2833a0,'stats':_0x3c44df,'index':{'entries':_0x34b113[_0x173281(0x2eb)],'files':_0x34b113[_0x537513(0x283)],'sizeBytes':_0x34b113[_0x537513(0x18b)]}}));return;}if(_0xaf266c['startsWith'](_0x173281(0x9c)+'y/')){const _0x3d1c07=_0xaf266c['slice'](-0x3*0x4b+-0x2423+0x10*0x251);if(_0x3d1c07['includes']('..')||!_0x3d1c07[_0x537513(0x2a4)](_0x537513(0x107))){_0xd11ab9['statusCode']=0x1a2+-0x1481+-0x146f*-0x1,_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'error':_0x173281(0x278)+'le'}));return;}try{const _0x30054d=_0x1a4331[_0x173281(0x23e)+'nc'](resolve(MEMORY_DIR,_0x3d1c07),_0x537513(0x204));_0xd11ab9['end'](JSON['stringify']({'file':_0x3d1c07,'content':_0x30054d}));}catch{_0xd11ab9[_0x173281(0x1d0)]=0x253e+-0x1*-0x135f+0x3709*-0x1,_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'error':'File\x20not\x20f'+'ound'}));}return;}if(_0xaf266c===_0x173281(0x9c)+'y/save'&&_0x424589[_0x173281(0x11a)]===_0x537513(0xf4)){try{const {file:_0x14ec18,content:_0x535de6}=JSON[_0x173281(0x285)](_0x341686);if(_0x14ec18===_0x173281(0x102))_0x1a4331[_0x173281(0xae)+_0x537513(0x28c)](MEMORY_FILE,_0x535de6);else{if(_0x14ec18[_0x537513(0x2a4)](_0x173281(0x107))&&!_0x14ec18['includes']('..'))_0x1a4331[_0x173281(0xae)+'ync'](resolve(MEMORY_DIR,_0x14ec18),_0x535de6);else{_0xd11ab9[_0x173281(0x1d0)]=-0x1ba0+-0xe3e*-0x1+-0x2*-0x779,_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'error':'Invalid\x20fi'+'le'}));return;}}_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'ok':!![]}));}catch{_0xd11ab9[_0x173281(0x1d0)]=-0x15ce+-0x155c+0x2cba,_0xd11ab9['end'](JSON['stringify']({'error':_0x173281(0xe5)+_0x173281(0xd1)}));}return;}if(_0xaf266c===_0x537513(0x1b2)+'ns'){_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'plugins':getLoadedPlugins()}));return;}if(_0xaf266c==='/api/works'+_0x173281(0xf7)){try{const {listWorkspaces:_0x4c4024,getDefaultWorkspace:_0x43be01}=await import('../service'+_0x537513(0x281)+_0x537513(0x1b1)),{getCostByWorkspace:_0x5e792a}=await import(_0x173281(0x19a)+_0x537513(0x10e)+'js'),_0x5e526a=_0x5e792a(),_0x5ee900=_0x4c4024(),_0x135749=[_0x43be01(),..._0x5ee900],_0x1db293=_0x135749[_0x173281(0xee)](_0x4b3fa4=>({'name':_0x4b3fa4[_0x537513(0x218)],'purpose':_0x4b3fa4['purpose'],'emoji':_0x4b3fa4[_0x537513(0x1b4)]??null,'color':_0x4b3fa4[_0x173281(0x173)]??null,'cwd':_0x4b3fa4[_0x537513(0x1c9)],'channels':_0x4b3fa4['channels'],'stats':_0x5e526a[_0x4b3fa4['name']]??{'totalCost':0x0,'sessionCount':0x0,'messageCount':0x0,'toolUseCount':0x0}}));_0xd11ab9['end'](JSON[_0x173281(0x274)]({'workspaces':_0x1db293}));}catch(_0x341a1b){_0xd11ab9[_0x173281(0x1d0)]=0x3d7*-0x7+0x24f*0xf+-0x1c*0x35,_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'error':_0x341a1b instanceof Error?_0x341a1b[_0x173281(0x2a9)]:String(_0x341a1b)}));}return;}if(_0xaf266c===_0x537513(0xa1)&&_0x424589[_0x537513(0x11a)]===_0x173281(0x190)){const {getAllSessions:_0x2cf960}=await import(_0x173281(0x19a)+_0x537513(0x10e)+'js'),_0x43c85b=listProfiles(),_0x38a2ae=_0x2cf960(),_0x1280ac=new Map(Array[_0x173281(0x187)](_0x38a2ae['entries']())[_0x173281(0xee)](([_0x5465c8,_0x48d651])=>[Number(_0x5465c8),_0x48d651])),_0x5ba1b6=_0x43c85b['map'](_0x5fce9e=>{const _0x3be677=_0x537513,_0x39da92=_0x537513,_0x193c99=_0x1280ac['get'](_0x5fce9e[_0x3be677(0x241)]);return{..._0x5fce9e,'session':_0x193c99?{'isProcessing':_0x193c99['isProcessi'+'ng'],'totalCost':_0x193c99['totalCost'],'historyLength':_0x193c99[_0x3be677(0x1a1)]['length'],'effort':_0x193c99[_0x39da92(0x21c)],'voiceReply':_0x193c99['voiceReply'],'startedAt':_0x193c99['startedAt'],'messageCount':_0x193c99['messageCou'+'nt'],'toolUseCount':_0x193c99[_0x3be677(0x16b)+'nt'],'workingDir':_0x193c99[_0x3be677(0x2a6)],'hasActiveQuery':!!_0x193c99['abortContr'+_0x39da92(0x2d4)],'queuedMessages':_0x193c99[_0x39da92(0x2e6)+'ue'][_0x39da92(0x20f)]}:null};});_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'users':_0x5ba1b6}));return;}if(_0xaf266c[_0x173281(0x21e)]('/api/users'+'/')&&_0x424589[_0x537513(0x11a)]===_0x173281(0x29a)){if(isExposedWithoutPassword()){_0xd11ab9[_0x173281(0x1d0)]=0x162c+-0x1870+0x3d7,_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'error':_0x537513(0x25f)+'eb\x20exposed'+_0x173281(0x2d3)+_0x173281(0x18d)+'D'}));return;}const _0xd56ff8=parseInt(_0xaf266c[_0x537513(0x2f6)]('/')['pop']()||'0');if(!_0xd56ff8){_0xd11ab9[_0x173281(0x1d0)]=-0x1*0x16bb+0x2ef*-0x1+0x1b3a,_0xd11ab9['end'](JSON[_0x537513(0x274)]({'error':_0x173281(0xa8)+_0x537513(0x123)}));return;}const {deleteUser:_0x16122f}=await import(_0x173281(0x19a)+_0x537513(0x228)),_0x5f4fc9=_0x16122f(_0xd56ff8);_0xd11ab9[_0x173281(0x9b)](JSON['stringify']({'ok':!![],..._0x5f4fc9}));return;}if(_0xaf266c===_0x173281(0x2a0)){const _0x360210=getCustomTools();_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'tools':_0x360210}));return;}if(_0xaf266c===_0x173281(0x2a0)+_0x173281(0x23f)&&_0x424589['method']===_0x537513(0xf4)){try{const {name:_0x486e02,params:_0x2075b0}=JSON[_0x173281(0x285)](_0x341686);if(!_0x486e02){_0xd11ab9['statusCode']=0x3*-0x30c+0xa1a+0x9a,_0xd11ab9[_0x537513(0x9b)](JSON['stringify']({'error':_0x537513(0x155)+'me'}));return;}const _0x7ab5cc=await executeCustomTool(_0x486e02,_0x2075b0||{});_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'ok':!![],'output':_0x7ab5cc}));}catch(_0x5a027a){const _0x3a6db0=_0x5a027a instanceof Error?_0x5a027a[_0x173281(0x2a9)]:String(_0x5a027a);_0xd11ab9[_0x537513(0x9b)](JSON['stringify']({'error':_0x3a6db0}));}return;}if(_0xaf266c===_0x173281(0x189)){const {getMCPStatus:_0x53f0b8,getMCPTools:_0x2e064,hasMCPConfig:_0x4b44f8}=await import(_0x537513(0x19a)+'s/mcp.js'),_0x4948a0=_0x53f0b8(),_0x19ce2d=_0x2e064(),_0x3f99ad=MCP_CONFIG;let _0x43c9f2={'servers':{}};try{_0x43c9f2=JSON[_0x173281(0x285)](_0x1a4331[_0x537513(0x23e)+'nc'](_0x3f99ad,_0x173281(0x204)));}catch{}_0xd11ab9['end'](JSON[_0x537513(0x274)]({'servers':_0x4948a0,'tools':_0x19ce2d,'config':_0x43c9f2,'hasConfig':_0x4b44f8()}));return;}if(_0xaf266c===_0x173281(0x151)+'dd'&&_0x424589[_0x173281(0x11a)]===_0x173281(0xf4)){try{const {name:_0x4d725a,command:_0x11fea6,args:_0x30082e,url:_0xebdd5c,env:_0x531527,headers:_0x7c0ba7}=JSON[_0x173281(0x285)](_0x341686);if(!_0x4d725a){_0xd11ab9[_0x537513(0x1d0)]=-0x19*0x155+0x2*-0x3c6+0x7*0x60f,_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'error':_0x173281(0x9f)+_0x537513(0x132)}));return;}const _0x1e9869=MCP_CONFIG;let _0x10ccaa={'servers':{}};try{_0x10ccaa=JSON[_0x537513(0x285)](_0x1a4331[_0x537513(0x23e)+'nc'](_0x1e9869,_0x537513(0x204)));}catch{}const _0x4b49d4={};if(_0x11fea6){_0x4b49d4[_0x537513(0x220)]=_0x11fea6,_0x4b49d4[_0x537513(0x1db)]=_0x30082e||[];if(_0x531527)_0x4b49d4['env']=_0x531527;}else{if(_0xebdd5c){_0x4b49d4['url']=_0xebdd5c;if(_0x7c0ba7)_0x4b49d4[_0x173281(0x86)]=_0x7c0ba7;}else{_0xd11ab9['statusCode']=-0x6*-0x5cb+0x1535*-0x1+-0xbfd,_0xd11ab9['end'](JSON[_0x537513(0x274)]({'error':_0x173281(0x15b)+_0x537513(0x150)+_0x173281(0x132)}));return;}}_0x10ccaa[_0x537513(0xc3)][_0x4d725a]=_0x4b49d4,_0x1a4331['writeFileS'+'ync'](_0x1e9869,JSON[_0x173281(0x274)](_0x10ccaa,null,-0x15*-0x35+-0x45b+-0x1*-0x4)),_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'ok':!![],'note':'Restart\x20ne'+_0x173281(0x13d)+_0x173281(0x1d2)}));}catch(_0x333b02){_0xd11ab9[_0x537513(0x1d0)]=0x1*-0x2125+-0x1227+-0x11*-0x31c,_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'error':_0x333b02 instanceof Error?_0x333b02[_0x537513(0x2a9)]:String(_0x333b02)}));}return;}if(_0xaf266c===_0x537513(0x20a)+_0x173281(0x198)&&_0x424589['method']===_0x537513(0xf4)){try{const {name:_0x47512d}=JSON[_0x537513(0x285)](_0x341686),_0x59a380=MCP_CONFIG;let _0x79cd29={'servers':{}};try{_0x79cd29=JSON['parse'](_0x1a4331[_0x537513(0x23e)+'nc'](_0x59a380,_0x537513(0x204)));}catch{}delete _0x79cd29[_0x173281(0xc3)][_0x47512d],_0x1a4331[_0x173281(0xae)+_0x537513(0x28c)](_0x59a380,JSON[_0x537513(0x274)](_0x79cd29,null,-0x9*0x2a1+0x1145+-0x2*-0x333)),_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'ok':!![]}));}catch(_0x4de911){_0xd11ab9[_0x537513(0x1d0)]=-0x2149*-0x1+-0x1d*0x71+-0x12ec,_0xd11ab9['end'](JSON[_0x537513(0x274)]({'error':_0x4de911 instanceof Error?_0x4de911['message']:String(_0x4de911)}));}return;}if(_0xaf266c===_0x173281(0x22c)+'iscover'){const _0x5d99a9=[],{execSync:_0x31a393}=await import(_0x173281(0x27a)+_0x537513(0x247)),_0x1bb48b=[{'pkg':_0x173281(0x208)+_0x537513(0x18f)+_0x537513(0x1bb)+_0x537513(0x271),'name':_0x537513(0x201),'args':[_0x537513(0x27c)]},{'pkg':_0x537513(0x208)+_0x537513(0x18f)+_0x537513(0x194)+_0x173281(0x25e)+'h','name':_0x537513(0x258)+'ch','args':[]},{'pkg':_0x173281(0x208)+'extprotoco'+'l/server-g'+_0x173281(0x17d),'name':_0x173281(0x2be),'args':[]},{'pkg':'@modelcont'+_0x537513(0x18f)+'l/server-p'+'ostgres','name':_0x537513(0x1d1),'args':[]},{'pkg':_0x537513(0x208)+_0x173281(0x18f)+_0x537513(0x1c8)+'qlite','name':'sqlite','args':[]},{'pkg':_0x537513(0x208)+_0x173281(0x18f)+_0x537513(0x1c8)+'lack','name':'slack','args':[]},{'pkg':_0x537513(0x208)+_0x537513(0x18f)+'l/server-m'+'emory','name':_0x173281(0x26f),'args':[]},{'pkg':_0x173281(0x208)+_0x173281(0x18f)+_0x537513(0x280)+_0x537513(0x24c),'name':_0x173281(0x20e),'args':[]},{'pkg':_0x173281(0x208)+_0x173281(0x18f)+'l/server-f'+_0x173281(0x265),'name':'web-fetch','args':[]},{'pkg':_0x537513(0xeb)+_0x173281(0x191)+_0x173281(0xd3)+_0x173281(0x2f5)+'g','name':_0x173281(0x232)+_0x537513(0x169),'args':[]}];for(const _0x2ad9ef of _0x1bb48b){try{_0x31a393(_0x173281(0x1ed)+_0x2ad9ef[_0x537513(0x260)]+_0x537513(0x101),{'timeout':0x1388,'stdio':'pipe','env':{...process[_0x173281(0x302)],'PATH':process[_0x173281(0x302)]['PATH']+(_0x173281(0x1eb)+_0x537513(0x238)+'usr/local/'+'bin')}}),_0x5d99a9[_0x173281(0x13a)]({'name':_0x2ad9ef[_0x173281(0x218)],'command':_0x173281(0x136),'args':['-y',_0x2ad9ef[_0x537513(0x260)],..._0x2ad9ef[_0x173281(0x1db)]],'source':_0x173281(0x2bd)});}catch{try{_0x31a393('npm\x20list\x20-'+'g\x20'+_0x2ad9ef[_0x537513(0x260)]+_0x173281(0x178),{'timeout':0x1388,'stdio':_0x537513(0x2d9)}),_0x5d99a9[_0x173281(0x13a)]({'name':_0x2ad9ef[_0x173281(0x218)],'command':'npx','args':['-y',_0x2ad9ef['pkg'],..._0x2ad9ef[_0x537513(0x1db)]],'source':_0x537513(0x141)});}catch{}}}const _0x5f40c0=process['env'][_0x173281(0x2f3)]||process[_0x537513(0x302)][_0x173281(0x2e7)+'E']||'',_0x381453=[resolve(_0x5f40c0,_0x173281(0x2c2)+_0x173281(0x225)+_0x537513(0x290)+'config.jso'+'n'),resolve(_0x5f40c0,'Library/Ap'+_0x173281(0x81)+'Support/Cl'+_0x173281(0x225)+_0x173281(0x290)+'config.jso'+'n'),resolve(_0x5f40c0,_0x537513(0xff)+_0x537513(0x19d)+_0x537513(0xaa)+'desktop_co'+_0x173281(0x121))];for(const _0x2faacf of _0x381453){try{const _0x6a0780=JSON[_0x173281(0x285)](_0x1a4331['readFileSy'+'nc'](_0x2faacf,_0x537513(0x204)));if(_0x6a0780[_0x537513(0x119)])for(const [_0x45e985,_0x152b38]of Object['entries'](_0x6a0780[_0x537513(0x119)])){_0x152b38[_0x537513(0x220)]&&_0x5d99a9[_0x173281(0x13a)]({'name':_0x537513(0xbb)+_0x45e985,'command':_0x152b38[_0x173281(0x220)],'args':_0x152b38[_0x537513(0x1db)]||[],'source':_0x173281(0x1bd)+_0x537513(0xf5)});}}catch{}}_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'discovered':_0x5d99a9}));return;}if(_0xaf266c?.[_0x537513(0x1f2)](/^\/api\/skills\/detail\//)&&_0x424589[_0x537513(0x11a)]===_0x537513(0x190)){const _0x58a813=_0xaf266c[_0x537513(0x2f6)]('/')[_0x173281(0xd0)](),{getSkills:_0x39289a}=await import(_0x537513(0x19a)+_0x173281(0x2ce)+'s'),_0x5dbc93=_0x39289a()['find'](_0x5eb8aa=>_0x5eb8aa['id']===_0x58a813);_0x5dbc93?_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'ok':!![],'skill':_0x5dbc93})):(_0xd11ab9[_0x537513(0x1d0)]=0xe4d+0x1ccc+-0x2985,_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'error':_0x173281(0x1a2)+_0x537513(0x1c2)})));return;}if(_0xaf266c===_0x537513(0x2d0)+_0x173281(0x277)&&_0x424589[_0x173281(0x11a)]===_0x537513(0xf4)){try{const {id:_0x359ea4,name:_0xdbd93c,description:_0x6d5d07,triggers:_0x4315d0,category:_0x53f6d1,content:_0x3b396a,priority:_0x384387}=JSON['parse'](_0x341686);if(!_0x359ea4||!_0xdbd93c){_0xd11ab9[_0x173281(0x1d0)]=0x1*0x13ed+-0x61f*0x1+0x61f*-0x2,_0xd11ab9['end'](JSON[_0x537513(0x274)]({'error':'id\x20and\x20nam'+_0x537513(0x1b5)}));return;}if(typeof _0x359ea4!==_0x173281(0x1bf)||_0x359ea4[_0x537513(0x105)]('..')||_0x359ea4[_0x173281(0x105)]('/')||_0x359ea4[_0x173281(0x105)]('\x5c')||_0xfff6a9[_0x173281(0x300)](_0x359ea4)){_0xd11ab9['statusCode']=-0x1*0x1cd9+-0x836+0x269f,_0xd11ab9['end'](JSON[_0x173281(0x274)]({'error':_0x173281(0x125)+_0x173281(0x270)}));return;}const _0x34db3d=SKILLS_DIR,_0x1cb89d=resolve(_0x34db3d,_0x359ea4);if(!_0x1cb89d['startsWith'](_0x34db3d)){_0xd11ab9[_0x537513(0x1d0)]=-0x58*-0xc+0x13*-0x1ce+-0x1fba*-0x1,_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'error':_0x537513(0x125)+_0x173281(0x270)}));return;}if(!_0x1a4331[_0x537513(0x2c0)](_0x1cb89d))_0x1a4331[_0x173281(0x273)](_0x1cb89d,{'recursive':!![]});const _0x549167=[_0x173281(0xaf),_0x537513(0x1fe)+_0xdbd93c,_0x6d5d07?_0x537513(0xbe)+_0x537513(0x11f)+_0x6d5d07:'',_0x4315d0?_0x173281(0x154)+(Array[_0x173281(0x92)](_0x4315d0)?_0x4315d0[_0x537513(0x2a7)](',\x20'):_0x4315d0):'','priority:\x20'+(_0x384387||0x1cf7+-0x5*0x635+0x215),_0x173281(0x251)+(_0x53f6d1||'custom'),_0x537513(0xaf)][_0x537513(0x2fc)](Boolean)[_0x537513(0x2a7)]('\x0a');_0x1a4331['writeFileS'+_0x537513(0x28c)](resolve(_0x1cb89d,_0x537513(0x17f)),_0x549167+'\x0a\x0a'+(_0x3b396a||''));const {loadSkills:_0x52b45a}=await import('../service'+_0x537513(0x2ce)+'s');_0x52b45a(),_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'ok':!![]}));}catch(_0x393824){_0xd11ab9['statusCode']=0x9f6+-0x9b5+0x14f,_0xd11ab9['end'](JSON[_0x537513(0x274)]({'error':_0x393824 instanceof Error?_0x393824[_0x173281(0x2a9)]:String(_0x393824)}));}return;}if(_0xaf266c===_0x173281(0x2d0)+_0x173281(0x1a5)&&_0x424589[_0x173281(0x11a)]==='POST'){try{const {id:_0x3c35a5,content:_0x10de95}=JSON[_0x537513(0x285)](_0x341686);if(typeof _0x3c35a5!==_0x173281(0x1bf)||_0x3c35a5[_0x173281(0x105)]('..')||_0x3c35a5[_0x537513(0x105)]('/')||_0x3c35a5[_0x173281(0x105)]('\x5c')||_0xfff6a9[_0x173281(0x300)](_0x3c35a5)){_0xd11ab9[_0x537513(0x1d0)]=-0x1057+0x581*-0x1+0x5da*0x4,_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'error':'Invalid\x20sk'+_0x173281(0x270)}));return;}if(!resolve(SKILLS_DIR,_0x3c35a5)[_0x173281(0x21e)](SKILLS_DIR)){_0xd11ab9[_0x537513(0x1d0)]=0x53*0x3+-0x3*-0x55d+0x4*-0x3e0,_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'error':'Invalid\x20sk'+_0x537513(0x270)}));return;}const _0x525057=resolve(SKILLS_DIR,_0x3c35a5,_0x537513(0x17f));if(!_0x1a4331[_0x537513(0x2c0)](_0x525057)){const _0x39710d=resolve(SKILLS_DIR,_0x3c35a5+_0x173281(0x107));if(_0x1a4331[_0x173281(0x2c0)](_0x39710d))_0x1a4331['writeFileS'+'ync'](_0x39710d,_0x10de95);else{_0xd11ab9[_0x537513(0x1d0)]=-0x1afa+-0x1e19+-0x8f*-0x69,_0xd11ab9['end'](JSON[_0x173281(0x274)]({'error':_0x173281(0x1a2)+'found'}));return;}}else _0x1a4331[_0x173281(0xae)+_0x537513(0x28c)](_0x525057,_0x10de95);const {loadSkills:_0x4c85ae}=await import(_0x173281(0x19a)+_0x537513(0x2ce)+'s');_0x4c85ae(),_0xd11ab9['end'](JSON[_0x537513(0x274)]({'ok':!![]}));}catch(_0x35a643){_0xd11ab9[_0x173281(0x1d0)]=0x12*-0x1c0+0x1e45+0x2cb,_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'error':_0x35a643 instanceof Error?_0x35a643[_0x173281(0x2a9)]:String(_0x35a643)}));}return;}if(_0xaf266c===_0x173281(0x2d0)+_0x173281(0xb5)&&_0x424589[_0x537513(0x11a)]===_0x173281(0xf4)){try{const {id:_0x35e3e8}=JSON[_0x173281(0x285)](_0x341686);if(typeof _0x35e3e8!==_0x537513(0x1bf)||_0x35e3e8[_0x173281(0x105)]('..')||_0x35e3e8[_0x173281(0x105)]('/')||_0x35e3e8[_0x173281(0x105)]('\x5c')||_0xfff6a9[_0x173281(0x300)](_0x35e3e8)){_0xd11ab9['statusCode']=0x133*-0x2+-0x261d*0x1+0x2a13,_0xd11ab9['end'](JSON[_0x173281(0x274)]({'error':'Invalid\x20sk'+_0x537513(0x270)}));return;}const _0x47def4=resolve(SKILLS_DIR,_0x35e3e8),_0x22c317=resolve(SKILLS_DIR,_0x35e3e8+_0x537513(0x107));if(_0x1a4331[_0x173281(0x2c0)](_0x47def4))_0x1a4331[_0x173281(0xd8)](_0x47def4,{'recursive':!![]});else{if(_0x1a4331[_0x173281(0x2c0)](_0x22c317))_0x1a4331[_0x173281(0x17c)](_0x22c317);else{_0xd11ab9['statusCode']=0x2*0x7aa+0x12f8*-0x1+0x538*0x1,_0xd11ab9['end'](JSON['stringify']({'error':_0x173281(0x1a2)+_0x537513(0x1c2)}));return;}}const {loadSkills:_0x5d6d2a}=await import('../service'+_0x537513(0x2ce)+'s');_0x5d6d2a(),_0xd11ab9['end'](JSON[_0x173281(0x274)]({'ok':!![]}));}catch(_0xb79ee5){_0xd11ab9[_0x173281(0x1d0)]=0x1e6e+-0x2*-0x72e+-0x2b3a,_0xd11ab9['end'](JSON[_0x173281(0x274)]({'error':_0xb79ee5 instanceof Error?_0xb79ee5[_0x173281(0x2a9)]:String(_0xb79ee5)}));}return;}if(_0xaf266c===_0x173281(0x2f7)+'g'){_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'providers':config[_0x173281(0x176)+_0x173281(0x126)],'primaryProvider':config[_0x173281(0x2b3)+_0x173281(0x88)],'allowedUsers':config[_0x537513(0x1e5)+'rs'],'hasKeys':{'groq':!!config[_0x537513(0x244)]['groq'],'openai':!!config[_0x173281(0x244)][_0x173281(0x29d)],'google':!!config[_0x173281(0x244)][_0x537513(0x2c4)],'nvidia':!!config['apiKeys'][_0x537513(0x2d2)],'openrouter':!!config[_0x537513(0x244)][_0x173281(0x1e4)]}}));return;}if(_0xaf266c==='/api/sessi'+_0x173281(0x28e)){const _0x500710=getAllSessions(),_0x87d5cf=listProfiles(),_0x22e4b8=Array['from'](_0x500710['entries']())[_0x173281(0xee)](([_0x97c4c3,_0xc3a356])=>{const _0x163e61=_0x173281,_0x3c4ef4=_0x537513,_0x334257=Number(_0x97c4c3[_0x163e61(0x2f6)](':')['pop']()),_0x20ce85=_0x87d5cf[_0x163e61(0x296)](_0xd07db9=>_0xd07db9[_0x3c4ef4(0x241)]===_0x334257);return{'userId':_0x97c4c3,'name':_0x20ce85?.['name']||'User\x20'+_0x97c4c3,'username':_0x20ce85?.[_0x163e61(0x12c)],'messageCount':_0xc3a356[_0x3c4ef4(0x1ba)+'nt'],'toolUseCount':_0xc3a356[_0x163e61(0x16b)+'nt'],'totalCost':_0xc3a356['totalCost'],'totalInputTokens':_0xc3a356['totalInput'+_0x3c4ef4(0x21d)]||-0x1cab+-0x7*-0x452+-0x193,'totalOutputTokens':_0xc3a356[_0x163e61(0x196)+_0x3c4ef4(0x1fa)]||0x19*-0x18+-0x12dd+0x1535,'effort':_0xc3a356[_0x3c4ef4(0x21c)],'startedAt':_0xc3a356[_0x163e61(0x2de)],'lastActivity':_0xc3a356['lastActivi'+'ty'],'historyLength':_0xc3a356[_0x163e61(0x1a1)][_0x163e61(0x20f)],'isProcessing':_0xc3a356[_0x3c4ef4(0x298)+'ng'],'provider':Object[_0x163e61(0x248)](_0xc3a356[_0x163e61(0xef)+_0x163e61(0xca)])['join'](',\x20')||_0x3c4ef4(0x25a)};});_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'sessions':_0x22e4b8}));return;}if(_0xaf266c[_0x173281(0x1f2)](/^\/api\/sessions\/\d+\/history$/)){const _0x194274=parseInt(_0xaf266c[_0x173281(0x2f6)]('/')[-0xd5d*0x1+0x1dc3*-0x1+-0x4cb*-0x9]),_0x5765e1=getSession(_0x194274);_0xd11ab9[_0x173281(0x9b)](JSON['stringify']({'userId':_0x194274,'history':_0x5765e1[_0x173281(0x1a1)][_0x173281(0xee)](_0x5ed05c=>({'role':_0x5ed05c[_0x173281(0x2a2)],'content':_0x5ed05c[_0x537513(0x124)]['slice'](0x29a+-0x35*-0x7c+0xe23*-0x2,0x2482+-0x455*-0x1+-0x2107*0x1)}))}));return;}if(_0xaf266c==='/api/files'){const _0x3b8a8c=new URLSearchParams((_0x424589[_0x537513(0x29e)]||'')[_0x537513(0x2f6)]('?')[0x134d+-0x464*-0x2+0x3*-0x95c]||''),_0x1d3a88=_0x3b8a8c[_0x173281(0x2c8)](_0x537513(0x2ad))||'',_0x16b2e3=resolve(BOT_ROOT,_0x1d3a88||'.');if(!_0x16b2e3[_0x173281(0x21e)](BOT_ROOT)){_0xd11ab9[_0x173281(0x1d0)]=0x2*0x949+0x1f25*0x1+-0x13c*0x27,_0xd11ab9['end'](JSON['stringify']({'error':_0x173281(0x193)+_0x537513(0x2cc)}));return;}try{const _0x58e09f=_0x1a4331[_0x173281(0x249)](_0x16b2e3);if(_0x58e09f[_0x173281(0xb2)+'y']()){const _0x2979b3=_0x1a4331[_0x173281(0x243)+'c'](_0x16b2e3,{'withFileTypes':!![]})['filter'](_0x1efa69=>!_0x1efa69[_0x537513(0x218)][_0x537513(0x21e)]('.')&&_0x1efa69[_0x173281(0x218)]!==_0x537513(0x29b)+'es')['map'](_0x2daaac=>({'name':_0x2daaac['name'],'type':_0x2daaac['isDirector'+'y']()?_0x173281(0x288):'file','size':_0x2daaac['isFile']()?_0x1a4331[_0x537513(0x249)](resolve(_0x16b2e3,_0x2daaac['name']))[_0x537513(0x1b8)]:0x2319+0x22df+-0x4*0x117e,'modified':_0x1a4331[_0x537513(0x249)](resolve(_0x16b2e3,_0x2daaac[_0x173281(0x218)]))['mtimeMs']}))[_0x537513(0x286)]((_0x587cc7,_0xc5989a)=>{const _0x57cce1=_0x537513,_0x14f789=_0x537513;if(_0x587cc7['type']!==_0xc5989a['type'])return _0x587cc7[_0x57cce1(0x158)]===_0x14f789(0x288)?-(0x7*0x6b+-0x13*0x1c9+-0x17*-0x159):-0xce8+-0xc20+-0x1d*-0xdd;return _0x587cc7[_0x57cce1(0x218)][_0x14f789(0x8a)+_0x57cce1(0x2f1)](_0xc5989a[_0x57cce1(0x218)]);});_0xd11ab9['end'](JSON[_0x537513(0x274)]({'path':_0x1d3a88||'.','entries':_0x2979b3}));}else{const _0x4fca19=_0xfff6a9[_0x537513(0xab)](_0x16b2e3)[_0x537513(0x1d3)+'e'](),_0x252719=new Set([_0x537513(0x107),_0x537513(0x2d8),_0x537513(0x1d8),_0x173281(0x1dc),_0x173281(0x2d1),'.jsx','.tsx','.css',_0x173281(0x250),_0x537513(0x161),'.xml',_0x173281(0x1b3),_0x537513(0xce),_0x537513(0x1d6),_0x173281(0x2f8),_0x173281(0x295),_0x173281(0x1c1),_0x537513(0xa2),_0x537513(0xd4),_0x173281(0x282),_0x173281(0x1e9),_0x537513(0x13c),_0x173281(0x8b),_0x537513(0x2bc),_0x537513(0x14f),'.go',_0x537513(0x2cb),_0x537513(0x2b2),_0x173281(0x2e1),'.c',_0x537513(0x239),'.h',_0x173281(0x2d6),_0x537513(0x2b6),_0x537513(0xfa),_0x537513(0x18c),_0x173281(0x2ec),_0x537513(0x152),'.dockerfil'+'e',_0x537513(0x275),_0x537513(0x29f)+'utes','.editorcon'+_0x173281(0x120),_0x537513(0x15a)+'c',_0x537513(0x2db),_0x173281(0x16a),_0x537513(0x1af),_0x537513(0x212),_0x537513(0xec),_0x537513(0xb8),'.csv','.tsv',_0x537513(0xe7),_0x173281(0x112),_0x173281(0x272),_0x537513(0x1ee),'.vue',_0x173281(0x263),_0x173281(0x160)]),_0x5026e7=new Set([_0x173281(0x22e),_0x173281(0x159),_0x173281(0x1a8),_0x537513(0x2cf),_0x173281(0x192),_0x173281(0x1de)+'e',_0x173281(0x24a),'justfile',_0x173281(0x117),_0x537513(0x25d),'license','licence',_0x173281(0x14c),'changelog',_0x173281(0x1e2),'contributo'+'rs']),_0x2bfe82=_0xfff6a9[_0x173281(0xb3)](_0x16b2e3)[_0x173281(0x1d3)+'e'](),_0x3650cc=_0x5026e7[_0x537513(0x97)](_0x2bfe82),_0x2d2f18=_0x252719[_0x537513(0x97)](_0x4fca19)||_0x3650cc||!_0x4fca19&&_0x58e09f[_0x537513(0x1b8)]<-0xd282+0x9f76*-0x1+0x2f898;if(_0x58e09f[_0x173281(0x1b8)]>0x30833*-0x1+0x777f+0xa31d4)_0xd11ab9[_0x537513(0x9b)](JSON['stringify']({'path':_0x1d3a88,'content':_0x537513(0x14b)+'large:\x20'+(_0x58e09f['size']/(-0x26e+0xc9f+-0x631))['toFixed'](0x4bb*0x2+-0x634*0x6+0x1bc3)+(_0x537513(0x1f7)+_0x537513(0x230)),'size':_0x58e09f[_0x173281(0x1b8)]}));else{if(_0x2d2f18)try{const _0x5f21b1=_0x1a4331['readFileSy'+'nc'](_0x16b2e3,_0x173281(0x204)),_0x524221=[..._0x5f21b1[_0x173281(0xdb)](0x18f3+0x22f1+-0x3be4,0x1cad+-0x3*0x87+-0x35*0x70)]['filter'](_0x67a086=>_0x67a086==='\x00')['length'];_0x524221>0x883*-0x3+0x228f+-0xd*0xaa?_0xd11ab9['end'](JSON[_0x537513(0x274)]({'path':_0x1d3a88,'content':null,'size':_0x58e09f[_0x537513(0x1b8)],'binary':!![]})):_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'path':_0x1d3a88,'content':_0x5f21b1,'size':_0x58e09f['size']}));}catch{_0xd11ab9['end'](JSON[_0x173281(0x274)]({'path':_0x1d3a88,'content':null,'size':_0x58e09f[_0x173281(0x1b8)],'binary':!![]}));}else _0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'path':_0x1d3a88,'content':null,'size':_0x58e09f[_0x537513(0x1b8)],'binary':!![]}));}}}catch{_0xd11ab9['statusCode']=0x764*-0x1+-0x2b*-0x4a+-0x376,_0xd11ab9['end'](JSON[_0x173281(0x274)]({'error':_0x537513(0x16f)}));}return;}if(_0xaf266c===_0x173281(0x164)+_0x537513(0x135)&&_0x424589[_0x173281(0x11a)]===_0x173281(0xf4)){try{const {path:_0x5e3293,content:_0x239dc4}=JSON[_0x173281(0x285)](_0x341686),_0x2c401b=resolve(BOT_ROOT,_0x5e3293);if(!_0x2c401b[_0x537513(0x21e)](BOT_ROOT)){_0xd11ab9[_0x173281(0x1d0)]=-0x11*0x1f6+0xade+0x4cf*0x5,_0xd11ab9[_0x173281(0x9b)](JSON['stringify']({'error':_0x537513(0x193)+_0x537513(0x2cc)}));return;}_0x1a4331[_0x537513(0xae)+_0x173281(0x28c)](_0x2c401b,_0x239dc4),_0xd11ab9['end'](JSON[_0x537513(0x274)]({'ok':!![]}));}catch(_0x1a9e4d){_0xd11ab9[_0x537513(0x1d0)]=0x218d+0xf22+-0x2f1f;const _0x2b3426=_0x1a9e4d instanceof Error?_0x1a9e4d[_0x173281(0x2a9)]:_0x537513(0xe5)+_0x537513(0xd1);_0xd11ab9['end'](JSON['stringify']({'error':_0x2b3426}));}return;}if(_0xaf266c===_0x173281(0x164)+_0x537513(0x1f8)&&_0x424589[_0x537513(0x11a)]==='POST'){try{const {path:_0x1bd03f}=JSON[_0x173281(0x285)](_0x341686),_0x1375a6=resolve(BOT_ROOT,_0x1bd03f);if(!_0x1375a6[_0x173281(0x21e)](BOT_ROOT)){_0xd11ab9[_0x173281(0x1d0)]=-0x2f*0x7f+-0x16*0xb5+0x2872,_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'error':_0x173281(0x193)+_0x537513(0x2cc)}));return;}const _0x31ee98=['.env',_0x173281(0x14d)+'on',_0x173281(0x1be)+'son',_0x173281(0x202)+_0x537513(0x1e0)],_0x31c2e9=_0xfff6a9['basename'](_0x1375a6);if(_0x31ee98[_0x173281(0x105)](_0x31c2e9)){_0xd11ab9['statusCode']=-0x2338+-0x57b+0x2*0x1523,_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'error':_0x31c2e9+(_0x537513(0x303)+_0x173281(0x2da)+_0x537513(0x21f))}));return;}if(!_0x1a4331[_0x173281(0x2c0)](_0x1375a6)){_0xd11ab9['statusCode']=0x3fe+0xc79+-0x67*0x25,_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'error':'File\x20not\x20f'+_0x173281(0x10c)}));return;}const _0x87b708=_0x1a4331['statSync'](_0x1375a6);if(_0x87b708[_0x173281(0xb2)+'y']()){_0xd11ab9[_0x537513(0x1d0)]=-0x1*0x14dd+-0x4b9+0x116*0x19,_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'error':_0x537513(0x17b)+_0x173281(0x2fd)+_0x173281(0x15e)}));return;}_0x1a4331[_0x173281(0x17c)](_0x1375a6),_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'ok':!![]}));}catch(_0x480406){_0xd11ab9[_0x537513(0x1d0)]=0x2622+0x221b+-0x178f*0x3;const _0x8e9473=_0x480406 instanceof Error?_0x480406[_0x173281(0x2a9)]:'Invalid\x20re'+_0x537513(0xd1);_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'error':_0x8e9473}));}return;}if(_0xaf266c===_0x173281(0x1cd)+_0x537513(0x23d)&&_0x424589[_0x537513(0x11a)]===_0x173281(0xf4)){try{const {command:_0x80bd78}=JSON[_0x537513(0x285)](_0x341686);if(!_0x80bd78){_0xd11ab9[_0x537513(0x1d0)]=-0xa35+-0x68b*0x5+0x2c7c,_0xd11ab9['end'](JSON[_0x173281(0x274)]({'error':_0x173281(0x2fe)}));return;}if(_0x80bd78['length']>0x2474+-0x5*0x84d+-0x1*-0x2c1d){_0xd11ab9[_0x537513(0x1d0)]=0x2539*-0x1+-0x1b77*0x1+0xa0*0x6a,_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'error':_0x537513(0x2e8)+_0x173281(0x1c0)+_0x537513(0x1f6)+_0x173281(0x261)}));return;}const _0x5d7586=typeof JSON[_0x537513(0x285)](_0x341686)['cwd']===_0x537513(0x1bf)?resolve(JSON['parse'](_0x341686)['cwd']):BOT_ROOT,_0x2a4654=execSync(_0x80bd78,{'cwd':_0x5d7586,'stdio':_0x173281(0x2d9),'timeout':0x1d4c0,'env':{...process[_0x537513(0x302)],'PATH':process[_0x173281(0x302)][_0x537513(0xb1)]+(':/opt/home'+_0x173281(0x238)+_0x173281(0x289)+'bin')}})[_0x537513(0x131)]();_0xd11ab9[_0x173281(0x9b)](JSON['stringify']({'output':_0x2a4654['slice'](-0x17*0x189+-0xf94+-0x745*-0x7,0x7*0x28d9+-0x2995d+-0x3020e*-0x1)}));}catch(_0x8c059d){const _0x5100a8=_0x8c059d,_0x3a8498=_0x5100a8[_0x173281(0x195)]?.[_0x173281(0x131)]()?.[_0x537513(0x25c)]()||'';_0xd11ab9[_0x173281(0x9b)](JSON['stringify']({'output':_0x3a8498||_0x5100a8[_0x173281(0x2a9)],'exitCode':0x1}));}return;}if(_0xaf266c==='/api/env'){try{const _0xa98ae2=_0x1a4331['existsSync'](ENV_FILE)?_0x1a4331[_0x537513(0x23e)+'nc'](ENV_FILE,'utf-8'):'',_0x552d30=_0xa98ae2[_0x173281(0x2f6)]('\x0a')[_0x537513(0x2fc)](_0x3755a8=>_0x3755a8[_0x173281(0x105)]('=')&&!_0x3755a8[_0x537513(0x21e)]('#')),_0x233df6=_0x552d30[_0x173281(0xee)](_0x5bade5=>{const _0x3049f4=_0x173281,_0x442fd5=_0x537513,[_0x148351,..._0x2c0e2d]=_0x5bade5[_0x3049f4(0x2f6)]('='),_0xdaa74f=_0x2c0e2d[_0x442fd5(0x2a7)]('=')[_0x442fd5(0x25c)](),_0x597c95=_0x148351[_0x3049f4(0x105)](_0x3049f4(0xb7))||_0x148351['includes'](_0x3049f4(0xd6))||_0x148351['includes']('PASSWORD')||_0x148351[_0x442fd5(0x105)](_0x3049f4(0x27e))?_0xdaa74f[_0x442fd5(0x20f)]>-0x6e1*-0x1+-0x45*0x19+-0x20?_0xdaa74f[_0x3049f4(0xdb)](-0x238a+0x1*-0x250f+0x3*0x1833,0x2394+-0xd88+-0x1608)+_0x3049f4(0x2b7)+_0xdaa74f[_0x3049f4(0xdb)](-(-0x114c+0x1a68+-0x1*0x918)):'****':_0xdaa74f;return{'key':_0x148351[_0x442fd5(0x25c)](),'value':_0x597c95,'hasValue':_0xdaa74f[_0x442fd5(0x20f)]>-0x220a+-0x31d*0x5+-0x583*-0x9};});_0xd11ab9['end'](JSON[_0x173281(0x274)]({'vars':_0x233df6}));}catch{_0xd11ab9[_0x537513(0x9b)](JSON['stringify']({'vars':[]}));}return;}if(_0xaf266c==='/api/env/s'+'et'&&_0x424589[_0x537513(0x11a)]==='POST'){try{const {key:_0x3b2678,value:_0x2b7468}=JSON['parse'](_0x341686);if(!_0x3b2678||typeof _0x3b2678!==_0x537513(0x1bf)||!_0x3b2678[_0x173281(0x1f2)](/^[A-Z_][A-Z0-9_]*$/)){_0xd11ab9[_0x537513(0x1d0)]=-0x2121+-0x9*-0x20e+-0xd*-0x13f,_0xd11ab9[_0x173281(0x9b)](JSON['stringify']({'error':_0x537513(0x301)+'y\x20name'}));return;}if(typeof _0x2b7468===_0x537513(0x1bf)&&/[\n\r]/[_0x173281(0x1b0)](_0x2b7468)){_0xd11ab9[_0x173281(0x1d0)]=0xb04+0xb5b+0x1*-0x14cf,_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'error':_0x537513(0x100)+_0x537513(0x1a7)+_0x537513(0x26c)+_0x173281(0x2f2)+_0x537513(0x139)}));return;}let _0x26bf09=_0x1a4331[_0x173281(0x2c0)](ENV_FILE)?_0x1a4331[_0x537513(0x23e)+'nc'](ENV_FILE,_0x173281(0x204)):'';const _0x280256=new RegExp('^'+_0x3b2678+'=.*$','m');_0x280256[_0x173281(0x1b0)](_0x26bf09)?_0x26bf09=_0x26bf09[_0x537513(0xb4)](_0x280256,_0x3b2678+'='+_0x2b7468):_0x26bf09=_0x26bf09[_0x537513(0x1df)]()+('\x0a'+_0x3b2678+'='+_0x2b7468+'\x0a'),writeSecure(ENV_FILE,_0x26bf09),_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'ok':!![],'note':'Restart\x20re'+'quired\x20for'+_0x537513(0x299)+_0x173281(0x2b9)+_0x537513(0xba)}));}catch{_0xd11ab9[_0x173281(0x1d0)]=-0x6d0+0x2*0x435+-0xa*0x1,_0xd11ab9[_0x173281(0x9b)](JSON['stringify']({'error':_0x537513(0xe5)+'quest'}));}return;}if(_0xaf266c==='/api/soul'){const _0x2ae148=getSoulContent();_0xd11ab9['end'](JSON['stringify']({'content':_0x2ae148}));return;}if(_0xaf266c===_0x173281(0xde)+_0x173281(0x253)&&_0x424589[_0x173281(0x11a)]===_0x173281(0xf4)){try{const {content:_0x454ec7}=JSON[_0x537513(0x285)](_0x341686),_0x3f3928=SOUL_FILE;_0x1a4331[_0x537513(0xae)+_0x173281(0x28c)](_0x3f3928,_0x454ec7),reloadSoul(),_0xd11ab9['end'](JSON[_0x173281(0x274)]({'ok':!![]}));}catch{_0xd11ab9[_0x537513(0x1d0)]=0xd0d+-0x1b02+0x1*0xf85,_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'error':_0x173281(0xe5)+_0x173281(0xd1)}));}return;}if(_0xaf266c===_0x173281(0x209)+_0x173281(0x127)){const _0x71f78e=[{'name':_0x537513(0x1d4),'key':_0x537513(0x1bc),'icon':'📱','configured':!!process[_0x537513(0x302)][_0x537513(0x1bc)]},{'name':_0x173281(0x1c4),'key':_0x173281(0x24f)+_0x173281(0x179),'icon':'🎮','configured':!!process[_0x537513(0x302)][_0x173281(0x24f)+_0x173281(0x179)]},{'name':_0x173281(0x10f),'key':'WHATSAPP_E'+_0x173281(0x15c),'icon':'💬','configured':process[_0x537513(0x302)]['WHATSAPP_E'+'NABLED']===_0x173281(0x28f)},{'name':_0x537513(0x29c),'key':_0x537513(0xe6)+'_URL','icon':'🔒','configured':!!process[_0x173281(0x302)][_0x173281(0xe6)+_0x173281(0x166)]},{'name':'Web\x20UI','key':_0x173281(0x11e),'icon':'🌐','configured':!![]}];_0xd11ab9[_0x537513(0x9b)](JSON['stringify']({'platforms':_0x71f78e}));return;}if(_0xaf266c===_0x173281(0x268)+'rt'&&_0x424589['method']==='POST'){const {scheduleGracefulRestart:_0x25f0a2}=await import('../service'+'s/restart.'+'js');_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'ok':!![],'note':_0x173281(0x1c5)+_0x173281(0x2b7)})),_0x25f0a2(0x56*0xf+0x1db3+0x1*-0x20c9);return;}if(_0xaf266c===_0x537513(0x1fc)+_0x173281(0xdc)&&_0x424589['method']===_0x173281(0xf4)){try{const {messages:_0x3c9451,format:_0x5bd134}=JSON['parse'](_0x341686);if(_0x5bd134==='json')_0xd11ab9[_0x173281(0x15d)](_0x537513(0x197)+'pe',_0x537513(0x8c)+_0x537513(0x1e1)),_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'export':_0x3c9451},null,-0xc7e+-0x1d4b+-0x337*-0xd));else{const _0x30cb20=_0x3c9451[_0x173281(0xee)](_0x570fe2=>{const _0xf06827=_0x173281,_0xec4321=_0x537513,_0x785da8=_0x570fe2[_0xf06827(0x2a2)]===_0xec4321(0x138)?_0xec4321(0xd5):_0x570fe2[_0xec4321(0x2a2)]==='assistant'?'**Alvin\x20Bo'+'t:**':_0xf06827(0x22f),_0x11422c=_0x570fe2[_0xf06827(0x245)]?_0xf06827(0x18a)+_0x570fe2['time']+')_':'';return''+_0x785da8+_0x11422c+'\x0a'+_0x570fe2[_0xf06827(0xa6)]+'\x0a';})[_0x173281(0x2a7)](_0x537513(0x130));_0xd11ab9[_0x537513(0x15d)](_0x537513(0x197)+'pe',_0x537513(0x16d)+_0x173281(0x237)),_0xd11ab9[_0x173281(0x9b)](_0x537513(0x2e4)+'ort\x20—\x20Alvi'+'n\x20Bot\x0a_'+new Date()[_0x537513(0x96)+_0x537513(0x1f5)]('de-DE')+_0x173281(0x264)+_0x30cb20);}}catch{_0xd11ab9['statusCode']=0x75*0x2+-0x1*0x1b91+0xe9*0x1f,_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'error':'Invalid\x20re'+_0x537513(0xd1)}));}return;}if(_0xaf266c===_0x537513(0x223)+_0x173281(0x294)&&_0x424589['method']===_0x537513(0x190)){try{const {getWhatsAppAdapter:_0x260c3c}=await import(_0x537513(0xa5)+_0x537513(0x174)+_0x173281(0x1b6)),_0x4b922c=_0x260c3c();if(!_0x4b922c){_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'groups':[],'error':'WhatsApp\x20n'+'icht\x20verbu'+_0x537513(0x2b8)}));return;}const _0x2e11ef=await _0x4b922c[_0x537513(0x2dd)]();_0xd11ab9['end'](JSON['stringify']({'groups':_0x2e11ef}));}catch(_0x1dd89e){_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'groups':[],'error':String(_0x1dd89e)}));}return;}if(_0xaf266c[_0x173281(0x1f2)](/^\/api\/whatsapp\/groups\/[^/]+\/participants$/)){try{const _0x576bba=decodeURIComponent(_0xaf266c[_0x537513(0x2f6)]('/')[-0xa1c+0x2*0x3e5+-0x1a*-0x17]),{getWhatsAppAdapter:_0x56101a}=await import(_0x173281(0xa5)+'ms/whatsap'+_0x173281(0x1b6)),_0x3f2fd0=_0x56101a();if(!_0x3f2fd0){_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'participants':[],'error':'WhatsApp\x20n'+_0x537513(0x12e)+_0x537513(0x2b8)}));return;}const _0x31264c=await _0x3f2fd0[_0x173281(0x1da)+_0x173281(0x236)](_0x576bba);_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'participants':_0x31264c}));}catch(_0x13a8ed){_0xd11ab9[_0x537513(0x9b)](JSON[_0x173281(0x274)]({'participants':[],'error':String(_0x13a8ed)}));}return;}if(_0xaf266c===_0x173281(0x223)+'app/group-'+'rules'&&_0x424589[_0x173281(0x11a)]===_0x173281(0x190)){const {getGroupRules:_0x35168d}=await import(_0x173281(0xa5)+_0x537513(0x174)+_0x537513(0x1b6));_0xd11ab9[_0x537513(0x9b)](JSON[_0x537513(0x274)]({'rules':_0x35168d()}));return;}if(_0xaf266c==='/api/whats'+_0x537513(0x13b)+_0x537513(0xf8)&&_0x424589[_0x173281(0x11a)]===_0x537513(0xf4)){try{const _0x3a671b=JSON[_0x537513(0x285)](_0x341686);if(!_0x3a671b[_0x173281(0x83)]){_0xd11ab9[_0x537513(0x1d0)]=-0x879+0x1499*-0x1+-0x51b*-0x6,_0xd11ab9['end'](JSON[_0x173281(0x274)]({'error':_0x173281(0xfc)+_0x173281(0xbd)+'lich'}));return;}const {upsertGroupRule:_0x5df03a}=await import(_0x173281(0xa5)+_0x173281(0x174)+_0x173281(0x1b6)),_0xad9e=_0x5df03a(_0x3a671b);_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'ok':!![],'rule':_0xad9e}));}catch(_0x2921a0){_0xd11ab9['statusCode']=0x1512+0xafc*0x2+-0x297a,_0xd11ab9[_0x173281(0x9b)](JSON[_0x537513(0x274)]({'error':String(_0x2921a0)}));}return;}if(_0xaf266c[_0x537513(0x1f2)](/^\/api\/whatsapp\/group-rules\//)&&_0x424589[_0x537513(0x11a)]===_0x173281(0x29a)){if(isExposedWithoutPassword()){_0xd11ab9[_0x173281(0x1d0)]=0x9c+0x1c13*0x1+0x4*-0x6c7,_0xd11ab9[_0x173281(0x9b)](JSON[_0x173281(0x274)]({'error':_0x537513(0x25f)+_0x173281(0xdf)+'\x20without\x20W'+_0x537513(0x18d)+'D'}));return;}const _0x328e3c=decodeURIComponent(_0xaf266c['split']('/')[_0x537513(0xdb)](0x32f+-0x93a+-0x21*-0x2f)[_0x537513(0x2a7)]('/')),{deleteGroupRule:_0x4d9d9b}=await import(_0x173281(0xa5)+'ms/whatsap'+_0x173281(0x1b6)),_0x5069fe=_0x4d9d9b(_0x328e3c);_0xd11ab9[_0x173281(0x9b)](JSON['stringify']({'ok':_0x5069fe}));return;}_0xd11ab9[_0x537513(0x1d0)]=0x1f8d*-0x1+0x18*0x120+0x1*0x621,_0xd11ab9[_0x537513(0x9b)](JSON['stringify']({'error':_0x173281(0x16f)}));}const chatClients=new Set();function _0x51ee(_0x58056f,_0x415c6f){_0x58056f=_0x58056f-(0x1d56*0x1+-0x1437+-0x89e);const _0x324a05=_0xf829();let _0x8661d4=_0x324a05[_0x58056f];if(_0x51ee['WpNNke']===undefined){var _0x1f6f59=function(_0x2e06e8){const _0x158984='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x5b2cc7='',_0x169f9e='',_0x53d983=_0x5b2cc7+_0x1f6f59;for(let _0xb66298=0x95a*0x3+-0x3*-0x21c+-0x2262,_0xe35eab,_0x37920b,_0x8190ec=-0x133*-0x13+0x1bbb+-0x3284;_0x37920b=_0x2e06e8['charAt'](_0x8190ec++);~_0x37920b&&(_0xe35eab=_0xb66298%(-0x191*-0x1+0x15*-0x71+-0x1ee*-0x4)?_0xe35eab*(-0x1*-0x1a21+-0x10ad+-0x934*0x1)+_0x37920b:_0x37920b,_0xb66298++%(-0x22d5+-0x14b+0xc0c*0x3))?_0x5b2cc7+=_0x53d983['charCodeAt'](_0x8190ec+(0xba*-0xb+-0xf58+0x1760))-(-0x14e8+-0x1476+0x64*0x6a)!==0x25*-0xcc+0x21b8+-0x43c?String['fromCharCode'](-0x4bc+0x268d+-0x20d2&_0xe35eab>>(-(0x5*-0xc7+0x33b+0x2*0x55)*_0xb66298&0xeaa+0x5b2+-0x1456)):_0xb66298:-0x10be+-0x1*0xf32+0x70*0x49){_0x37920b=_0x158984['indexOf'](_0x37920b);}for(let _0x356686=0x89b+0x4*0x104+0x8d*-0x17,_0x2a7995=_0x5b2cc7['length'];_0x356686<_0x2a7995;_0x356686++){_0x169f9e+='%'+('00'+_0x5b2cc7['charCodeAt'](_0x356686)['toString'](-0xb7c+0x2*-0xae4+-0x1*-0x2154))['slice'](-(0x1*0x9e8+0x101*0x17+-0x20fd));}return decodeURIComponent(_0x169f9e);};_0x51ee['sKBLZm']=_0x1f6f59,_0x51ee['dpgBTE']={},_0x51ee['WpNNke']=!![];}const _0x703db7=_0x324a05[-0x41d+0x4*-0x560+0x199d],_0x156966=_0x58056f+_0x703db7,_0x476170=_0x51ee['dpgBTE'][_0x156966];if(!_0x476170){const _0x15d70c=function(_0x56855c){this['yHLgzA']=_0x56855c,this['bKhbLZ']=[0x173a*-0x1+-0x1023+0x275e,0x1778+-0x228a+0xda*0xd,-0xdb6+0x11ed+-0x437],this['FXezGK']=function(){return'newState';},this['uGvxAH']='\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['BfbWWG']='[\x27|\x22].+[\x27|\x22];?\x20*}';};_0x15d70c['prototype']['FOXkSZ']=function(){const _0x5049de=new RegExp(this['uGvxAH']+this['BfbWWG']),_0x1d7eb7=_0x5049de['test'](this['FXezGK']['toString']())?--this['bKhbLZ'][0x7f4+0x1*-0x157a+-0x1*-0xd87]:--this['bKhbLZ'][0x1ee5+-0x1*0x10a1+-0x14c*0xb];return this['yjICOC'](_0x1d7eb7);},_0x15d70c['prototype']['yjICOC']=function(_0x36233f){if(!Boolean(~_0x36233f))return _0x36233f;return this['BoKZuK'](this['yHLgzA']);},_0x15d70c['prototype']['BoKZuK']=function(_0x21d5da){for(let _0x18525e=-0x18a+-0x931+0xabb,_0x521b96=this['bKhbLZ']['length'];_0x18525e<_0x521b96;_0x18525e++){this['bKhbLZ']['push'](Math['round'](Math['random']())),_0x521b96=this['bKhbLZ']['length'];}return _0x21d5da(this['bKhbLZ'][0x1908+0xb3*-0x24+0x24]);},new _0x15d70c(_0x51ee)['FOXkSZ'](),_0x8661d4=_0x51ee['sKBLZm'](_0x8661d4),_0x51ee['dpgBTE'][_0x156966]=_0x8661d4;}else _0x8661d4=_0x476170;return _0x8661d4;}broadcast['on'](_0x5471aa(0x157),_0x547657=>{const _0xb4e83b=_0x406ff9,_0x57affc=_0x5471aa;if(_0x547657[_0xb4e83b(0x22b)]!==_0x57affc(0x22d))return;const _0x3b0757=JSON['stringify']({'type':_0xb4e83b(0x2dc)+'r_msg','text':_0x547657['text'],'platform':_0x547657[_0xb4e83b(0x22b)],'userName':_0x547657['userName'],'ts':_0x547657['ts']});for(const _0x39ccb5 of chatClients){if(_0x39ccb5[_0xb4e83b(0x206)]===WebSocket['OPEN'])_0x39ccb5['send'](_0x3b0757);}}),broadcast['on'](_0x406ff9(0xe4)+_0x5471aa(0x9d),_0x15a647=>{const _0xf3754c=_0x406ff9,_0xbee523=_0x5471aa;if(_0x15a647['platform']!==_0xf3754c(0x22d))return;const _0x1b6cd0=JSON['stringify']({'type':_0xbee523(0x137)+_0xf3754c(0xa0)+'t','platform':_0x15a647[_0xbee523(0x22b)],'ts':_0x15a647['ts']});for(const _0x5cc8c6 of chatClients){if(_0x5cc8c6['readyState']===WebSocket['OPEN'])_0x5cc8c6[_0xf3754c(0x10a)](_0x1b6cd0);}}),broadcast['on'](_0x406ff9(0x85)+_0x5471aa(0x2bf),_0x400125=>{const _0x906837=_0x406ff9,_0xd572d2=_0x5471aa;if(_0x400125[_0x906837(0x22b)]!==_0xd572d2(0x22d))return;const _0x529e9a=JSON[_0x906837(0x274)]({'type':_0xd572d2(0x137)+_0x906837(0x1a9)+'a','delta':_0x400125[_0x906837(0x8e)],'platform':_0x400125[_0x906837(0x22b)],'ts':_0x400125['ts']});for(const _0x58830e of chatClients){if(_0x58830e['readyState']===WebSocket[_0x906837(0x242)])_0x58830e[_0xd572d2(0x10a)](_0x529e9a);}}),broadcast['on'](_0x406ff9(0x85)+_0x406ff9(0x2a5),_0x59fc59=>{const _0x47c7a4=_0x5471aa,_0x101648=_0x406ff9;if(_0x59fc59['platform']!==_0x47c7a4(0x22d))return;const _0x52ce8f=JSON['stringify']({'type':_0x47c7a4(0x137)+_0x47c7a4(0x18e),'cost':_0x59fc59['cost'],'platform':_0x59fc59['platform'],'ts':_0x59fc59['ts']});for(const _0x1d94d3 of chatClients){if(_0x1d94d3['readyState']===WebSocket['OPEN'])_0x1d94d3['send'](_0x52ce8f);}});function handleWebSocket(_0x28bcd7){const _0x47ca6b=_0x406ff9;_0x28bcd7['on'](_0x47ca6b(0x13f),(_0x2cb73d,_0x585f96)=>{const _0x55a226=_0x47ca6b,_0x247b00=_0x47ca6b;if(WEB_PASSWORD&&!checkAuth(_0x585f96)){_0x2cb73d[_0x55a226(0x1fd)](0x1*0x229+0x1*0x128b+-0x513,_0x55a226(0x2d7)+'ticated');return;}const _0x313128=_0x585f96[_0x247b00(0x29e)]||'/';if(_0x313128===_0x55a226(0x2a3)){addCanvasClient(_0x2cb73d);return;}console[_0x55a226(0x2f4)](_0x55a226(0x13e)+_0x247b00(0x11d)+_0x247b00(0x133)),chatClients[_0x247b00(0x1c3)](_0x2cb73d),_0x2cb73d['on'](_0x55a226(0x2a9),async _0x179b9e=>{const _0x4485e1=_0x55a226,_0x5398ba=_0x55a226;try{const _0x5730eb=JSON[_0x4485e1(0x285)](_0x179b9e[_0x5398ba(0x131)]());if(_0x5730eb[_0x5398ba(0x158)]===_0x4485e1(0x27d)){let {text:_0x4695c0,effort:_0x53205f,file:_0xc0eb30}=_0x5730eb;const _0x491698=_0x5730eb['target'],_0xe8e5c5=config[_0x5398ba(0x1e5)+'rs'][0x1*0xf95+-0x1eb0+0xf1b]||0x1327*0x1+-0x2509+-0xe*-0x147;let _0x1c4a7d;if(_0x491698===_0x5398ba(0x1f4)&&typeof _0x5730eb[_0x4485e1(0x115)]===_0x4485e1(0x1bf)&&_0x5730eb['sessionKey'][_0x5398ba(0x21e)](_0x4485e1(0x19f)))_0x1c4a7d=_0x5730eb[_0x4485e1(0x115)];else _0x491698===_0x4485e1(0x22d)?_0x1c4a7d=_0xe8e5c5:_0x1c4a7d=_0xe8e5c5;if(_0xc0eb30?.[_0x5398ba(0x12b)]&&_0xc0eb30?.[_0x5398ba(0x218)])try{const _0x1153df=resolve(DATA_DIR,_0x4485e1(0x20b)+'s');if(!_0x1a4331[_0x4485e1(0x2c0)](_0x1153df))_0x1a4331[_0x4485e1(0x273)](_0x1153df,{'recursive':!![]});const _0x3e5060=_0xc0eb30[_0x5398ba(0x218)][_0x4485e1(0xb4)](/[^a-zA-Z0-9._-]/g,'_'),_0x554294=resolve(_0x1153df,Date[_0x5398ba(0x293)]()+'_'+_0x3e5060),_0x5a1305=_0xc0eb30[_0x5398ba(0x12b)]['split'](',')[0x22d0+0x2f1+0x25c*-0x10]||_0xc0eb30[_0x5398ba(0x12b)];_0x1a4331[_0x5398ba(0xae)+'ync'](_0x554294,Buffer[_0x5398ba(0x187)](_0x5a1305,_0x5398ba(0x8f))),_0x4695c0=_0x4695c0[_0x4485e1(0xb4)](/\[File attached:.*?\]/,_0x5398ba(0x2ed)+'d:\x20'+_0x554294+']');}catch(_0x306dd9){console[_0x5398ba(0x9a)]('WebUI\x20file'+_0x4485e1(0xad)+_0x4485e1(0x14e),_0x306dd9);}const _0x2d432b=getRegistry(),_0x4f5ace=_0x2d432b['getActive'](),_0x5755de=_0x4f5ace['config']['type']===_0x4485e1(0x213),_0x14edef=getSession(_0x1c4a7d),_0x50541d={'prompt':_0x4695c0,'systemPrompt':buildSystemPrompt(_0x5755de,_0x14edef[_0x5398ba(0x267)],_0x491698==='telegram'?_0x4485e1(0x22d):_0x4485e1(0x1ce)+_0x5398ba(0x2f9)),'workingDir':_0x14edef[_0x5398ba(0x2a6)],'effort':_0x53205f||_0x14edef[_0x4485e1(0x21c)],'sessionId':_0x5755de?_0x14edef[_0x4485e1(0x118)]:null,'history':!_0x5755de?_0x14edef['history']:undefined};let _0x28ce6f=![],_0x519e79='';try{for await(const _0x34dab1 of _0x2d432b[_0x5398ba(0x22a)+_0x5398ba(0x2c6)](_0x50541d)){if(_0x2cb73d[_0x4485e1(0x206)]!==WebSocket[_0x5398ba(0x242)])break;switch(_0x34dab1[_0x5398ba(0x158)]){case _0x4485e1(0xa6):if(_0x34dab1['text'])_0x519e79=_0x34dab1[_0x4485e1(0xa6)];_0x2cb73d[_0x4485e1(0x10a)](JSON[_0x4485e1(0x274)]({'type':_0x4485e1(0xa6),'text':_0x34dab1[_0x5398ba(0xa6)],'delta':_0x34dab1[_0x4485e1(0x8e)]}));break;case _0x4485e1(0x1e7):_0x2cb73d['send'](JSON['stringify']({'type':'tool','name':_0x34dab1[_0x4485e1(0x1c6)],'input':_0x34dab1[_0x4485e1(0x28a)]}));break;case'done':_0x28ce6f=!![];if(_0x34dab1['text'])_0x519e79=_0x34dab1[_0x4485e1(0xa6)];if(_0x34dab1[_0x4485e1(0x118)])_0x14edef['sessionId']=_0x34dab1[_0x5398ba(0x118)];if(_0x34dab1[_0x5398ba(0x217)])_0x14edef[_0x4485e1(0x144)]+=_0x34dab1[_0x5398ba(0x217)];if(_0x34dab1[_0x5398ba(0x2b1)+'s'])_0x14edef[_0x4485e1(0x168)+_0x5398ba(0x21d)]=(_0x14edef[_0x4485e1(0x168)+_0x4485e1(0x21d)]||-0x18c4+-0x1*0x1d2b+0x35ef*0x1)+_0x34dab1[_0x5398ba(0x2b1)+'s'];if(_0x34dab1['outputToke'+'ns'])_0x14edef[_0x5398ba(0x196)+'tTokens']=(_0x14edef[_0x4485e1(0x196)+'tTokens']||-0x1162+0x11ed+0x8b*-0x1)+_0x34dab1[_0x5398ba(0x27b)+'ns'];_0x2cb73d[_0x4485e1(0x10a)](JSON[_0x5398ba(0x274)]({'type':_0x4485e1(0x292),'cost':_0x34dab1[_0x4485e1(0x217)],'sessionId':_0x34dab1[_0x5398ba(0x118)],'inputTokens':_0x34dab1['inputToken'+'s'],'outputTokens':_0x34dab1[_0x5398ba(0x27b)+'ns'],'sessionTokens':{'input':_0x14edef[_0x4485e1(0x168)+_0x5398ba(0x21d)]||0x1bc*0xa+-0x14*-0x1b9+-0x33cc,'output':_0x14edef[_0x5398ba(0x196)+_0x5398ba(0x1fa)]||-0x1780+-0x151f*0x1+0x2c9f}}));break;case _0x4485e1(0x9a):_0x2cb73d['send'](JSON[_0x4485e1(0x274)]({'type':_0x4485e1(0x9a),'error':_0x34dab1[_0x5398ba(0x9a)]})),_0x28ce6f=!![];break;case'fallback':_0x2cb73d[_0x5398ba(0x10a)](JSON[_0x5398ba(0x274)]({'type':'fallback','from':_0x34dab1[_0x5398ba(0x259)+_0x4485e1(0xc1)],'to':_0x34dab1[_0x5398ba(0x1f0)+'me']}));break;}}!_0x28ce6f&&_0x2cb73d[_0x4485e1(0x206)]===WebSocket['OPEN']&&_0x2cb73d[_0x4485e1(0x10a)](JSON[_0x4485e1(0x274)]({'type':_0x5398ba(0x292),'cost':0x0}));if(_0x491698===_0x4485e1(0x22d)&&_0x519e79[_0x5398ba(0x25c)]())try{const _0x126d22=await import(_0x5398ba(0x19a)+_0x5398ba(0x10d)+_0x5398ba(0x21a));_0x126d22[_0x5398ba(0xfd)](_0x4485e1(0x22d),String(_0xe8e5c5),_0x519e79);}catch(_0x42c046){console['error'](_0x5398ba(0xbc)+'legram\x20rel'+_0x5398ba(0xb6),_0x42c046);}}catch(_0x316e79){const _0x59c919=_0x316e79 instanceof Error?_0x316e79[_0x5398ba(0x2a9)]:String(_0x316e79);console[_0x4485e1(0x9a)](_0x4485e1(0x2c3)+_0x4485e1(0x2e0),_0x59c919),_0x2cb73d[_0x4485e1(0x206)]===WebSocket['OPEN']&&(_0x2cb73d['send'](JSON[_0x5398ba(0x274)]({'type':'error','error':_0x59c919})),!_0x28ce6f&&_0x2cb73d[_0x4485e1(0x10a)](JSON[_0x4485e1(0x274)]({'type':_0x5398ba(0x292),'cost':0x0})));}}if(_0x5730eb[_0x4485e1(0x158)]==='reset'){const _0xc3e3b8=_0x5730eb[_0x5398ba(0x257)],_0x239d38=config[_0x4485e1(0x1e5)+'rs'][-0xdfd*-0x2+0xa4a+0x13c*-0x1f]||0x13b1+0x9e1+-0xa*0x2f5;let _0x22b1d9;_0xc3e3b8==='tui'&&typeof _0x5730eb[_0x4485e1(0x115)]===_0x5398ba(0x1bf)&&_0x5730eb['sessionKey']['startsWith'](_0x5398ba(0x19f))?_0x22b1d9=_0x5730eb[_0x4485e1(0x115)]:_0x22b1d9=_0x239d38,resetSession(_0x22b1d9),_0x2cb73d[_0x5398ba(0x10a)](JSON[_0x4485e1(0x274)]({'type':_0x5398ba(0xc7),'ok':!![]}));}}catch(_0xba17e){const _0x2b196e=_0xba17e instanceof Error?_0xba17e[_0x4485e1(0x2a9)]:String(_0xba17e);_0x2cb73d[_0x4485e1(0x10a)](JSON['stringify']({'type':_0x4485e1(0x9a),'error':_0x2b196e}));}}),_0x2cb73d['on']('close',()=>{const _0x219c75=_0x247b00,_0x51c341=_0x247b00;console['log'](_0x219c75(0x13e)+_0x51c341(0x134)+_0x51c341(0x104)),chatClients[_0x219c75(0x2cd)](_0x2cb73d);});});}function handleWebRequest(_0x4a136f,_0x571ccb){const _0x3b728b=_0x5471aa;let _0x392891='';_0x4a136f['on']('data',_0x2c153a=>{_0x392891+=_0x2c153a;}),_0x4a136f['on'](_0x3b728b(0x9b),()=>{const _0x591ece=_0x3b728b,_0x42a079=_0x3b728b,_0x172399=(_0x4a136f[_0x591ece(0x29e)]||'/')[_0x42a079(0x2f6)]('?')[-0x240c+0x51e+0x1eee];if(_0x172399[_0x591ece(0x21e)](_0x42a079(0x26e))){handleOpenAICompat(_0x4a136f,_0x571ccb,_0x172399,_0x392891);return;}if(_0x172399[_0x42a079(0x21e)]('/api/')){handleAPI(_0x4a136f,_0x571ccb,_0x172399,_0x392891);return;}if(_0x172399[_0x42a079(0x21e)](_0x591ece(0x171))){handleAPI(_0x4a136f,_0x571ccb,_0x172399,_0x392891);return;}if(WEB_PASSWORD&&!checkAuth(_0x4a136f)&&_0x172399!==_0x42a079(0x2ea)+'l'){_0x571ccb['writeHead'](0x3*0x997+0x10e6+-0x1*0x2c7d,{'Location':_0x42a079(0x2ea)+'l'}),_0x571ccb[_0x591ece(0x9b)]();return;}if(_0x172399===_0x591ece(0x28b)){const _0x4b68f9=resolve(PUBLIC_DIR,_0x591ece(0x183)+'l');try{const _0x4c9f26=_0x1a4331[_0x42a079(0x23e)+'nc'](_0x4b68f9);_0x571ccb[_0x591ece(0x15d)](_0x42a079(0x197)+'pe',_0x591ece(0x1b7)),_0x571ccb[_0x591ece(0x9b)](_0x4c9f26);}catch{_0x571ccb[_0x42a079(0x1d0)]=0x41f+0x9*0x224+-0x1*0x15cf,_0x571ccb[_0x591ece(0x9b)](_0x591ece(0x16f));}return;}let _0x3a261f=_0x172399==='/'?_0x591ece(0x2e5)+'l':_0x172399;_0x3a261f=resolve(PUBLIC_DIR,_0x3a261f[_0x42a079(0xdb)](-0xb6f*-0x2+-0x1dab+0x6ce));if(!_0x3a261f[_0x42a079(0x21e)](PUBLIC_DIR)){_0x571ccb[_0x591ece(0x1d0)]=0x1da1+0xeab*-0x1+-0xd63,_0x571ccb[_0x42a079(0x9b)]('Forbidden');return;}try{const _0xc44fea=_0x1a4331[_0x42a079(0x23e)+'nc'](_0x3a261f),_0x386796=_0xfff6a9['extname'](_0x3a261f);_0x571ccb[_0x591ece(0x15d)]('Content-Ty'+'pe',MIME[_0x386796]||_0x591ece(0x8c)+_0x591ece(0xe2)+_0x591ece(0x1cb)),_0x571ccb['end'](_0xc44fea);}catch{_0x571ccb[_0x42a079(0x1d0)]=0x83*-0x3+-0x1a*0x11f+0x2043,_0x571ccb[_0x591ece(0x9b)](_0x591ece(0x16f));}});}export function startWebServer(){const _0x19514b=_0x5471aa,_0x579ef9=_0x406ff9;stopRequested=![],scheduleBindAttempt(parseInt(process[_0x19514b(0x302)][_0x19514b(0x11e)]||'3100',0x30b*0x4+0x1bcd*0x1+-0x27ef*0x1),-0x2*0x6fb+0x1*-0xec9+0x1cbf);}function scheduleBindAttempt(_0x28ff85,_0x57d588){const _0xd1f860=_0x5471aa,_0x35c4be=_0x406ff9;if(stopRequested)return;const _0x1e08d5=parseInt(process['env'][_0xd1f860(0x11e)]||_0x35c4be(0x12f)),_0x519c64=_0x14bdfc[_0xd1f860(0x2ae)+'er'](handleWebRequest);let _0x3e809e=![];const _0x421127=()=>{const _0x3e31c3=_0xd1f860,_0x11728e=_0x35c4be;try{_0x519c64[_0x3e31c3(0x113)+_0x11728e(0x216)](_0x11728e(0x9a));}catch{}try{_0x519c64[_0x11728e(0x1fd)](()=>{});}catch{}},_0x1022fa=_0x1f54ed=>{const _0x471f02=_0x35c4be,_0x1fe5c0=_0xd1f860;if(_0x3e809e)return;_0x3e809e=!![],_0x421127();if(stopRequested)return;const _0x9dbdb6=decideNextBindAction(_0x1f54ed,_0x57d588,{'originalPort':_0x1e08d5,'maxPortTries':MAX_PORT_TRIES,'backgroundRetryMs':BACKGROUND_RETRY_MS});if(_0x9dbdb6['type']===_0x471f02(0x8d)){console[_0x1fe5c0(0x16e)](_0x1fe5c0(0x177)+'\x20'+_0x28ff85+_0x1fe5c0(0x142)+(_0x1f54ed[_0x1fe5c0(0x182)]||_0x1f54ed[_0x471f02(0x2a9)])+(_0x1fe5c0(0x252)+'\x20')+_0x9dbdb6['port']),scheduleBindAttempt(_0x9dbdb6[_0x471f02(0x12a)],_0x9dbdb6[_0x471f02(0xb0)]);return;}console[_0x1fe5c0(0x16e)]('[web]\x20bind'+_0x1fe5c0(0x181)+(_0x1f54ed['code']||_0x1f54ed[_0x1fe5c0(0x2a9)])+_0x471f02(0x284)+(_0x1fe5c0(0x279)+'f\x20'+_0x9dbdb6[_0x1fe5c0(0x287)]/(0x1edf*-0x1+-0x149c+0x3763)+(_0x471f02(0x1ca)+_0x1fe5c0(0x2c7)+'\x20')+_0x9dbdb6[_0x471f02(0x12a)]+'.\x20')+(_0x1fe5c0(0xcf)+_0x471f02(0x147)+_0x471f02(0x2e2)+'mains\x20live'+'.')),bindRetryTimer=setTimeout(()=>{const _0x220f8e=_0x1fe5c0;bindRetryTimer=null,scheduleBindAttempt(_0x9dbdb6[_0x220f8e(0x12a)],0x61*-0x55+0xb21+-0x4c*-0x47);},_0x9dbdb6[_0x471f02(0x287)]);};_0x519c64['on'](_0x35c4be(0x9a),_0x1022fa);try{const _0x570513=config[_0xd1f860(0x210)]==='*'||config[_0x35c4be(0x210)]===''?undefined:config[_0xd1f860(0x210)];_0x519c64['listen'](_0x28ff85,_0x570513,()=>{const _0x5ee447=_0x35c4be,_0xba002d=_0xd1f860;if(_0x3e809e)return;_0x3e809e=!![];const _0x48a706=new WebSocketServer({'server':_0x519c64});handleWebSocket(_0x48a706),currentServer=_0x519c64,wsServerRef=_0x48a706,actualWebPort=_0x28ff85,_0x519c64[_0x5ee447(0x1aa)+_0xba002d(0xf6)](_0x5ee447(0x9a),_0x1022fa),_0x519c64['on'](_0x5ee447(0x9a),_0x479b39=>{const _0x326c41=_0xba002d,_0x4f6333=_0x5ee447;console[_0x326c41(0x16e)]('[web]\x20post'+_0x4f6333(0x2ca)+_0x326c41(0xd9)+'ignored):\x20'+_0x479b39['message']);});const _0x20e574=_0x570513&&_0x570513!==_0x5ee447(0x1f1)&&_0x570513!==_0xba002d(0x1e8)?_0x5ee447(0x207)+_0x570513+':'+actualWebPort+(_0x570513===_0x5ee447(0x17e)?'\x20(LAN-reac'+_0xba002d(0x1a4):''):'http://loc'+_0x5ee447(0x108)+actualWebPort;console[_0x5ee447(0x2f4)](_0x5ee447(0xc0)+_0x20e574),actualWebPort!==_0x1e08d5&&console[_0x5ee447(0x2f4)](_0x5ee447(0x1ac)+_0x1e08d5+(_0x5ee447(0x2fa)+_0xba002d(0xa4))+actualWebPort+_0x5ee447(0x240)),isExposedWithoutPassword()&&console[_0x5ee447(0x2f4)](_0xba002d(0xcc)+_0x5ee447(0x1e3)+_0xba002d(0xf2)+_0xba002d(0x26b)+'WORD:\x20muta'+_0x5ee447(0x172)+'endpoints\x20'+_0x5ee447(0x235)+_0x5ee447(0x175)+(_0xba002d(0x12d)+'sed\x20403).\x20'+_0xba002d(0x215)+_0x5ee447(0xfe)+_0xba002d(0x231)+_0xba002d(0xf3)+'\x20unlock\x20th'+_0xba002d(0x211))+(_0xba002d(0x224)+_0xba002d(0x2c1)+_0x5ee447(0x16c)+'EB_HOST=12'+'7.0.0.1.'));});}catch(_0x39d43c){_0x1022fa(_0x39d43c);}}export async function closeHttpServerGracefully(_0x259aa7){const _0x3de82e=_0x5471aa,_0x58d35a=_0x406ff9;if(!_0x259aa7['listening'])return;try{const _0x5d7cad=_0x259aa7;if(typeof _0x5d7cad['closeIdleC'+'onnections']===_0x3de82e(0x1e6))_0x5d7cad[_0x58d35a(0xe9)+_0x58d35a(0x246)]();if(typeof _0x5d7cad[_0x58d35a(0x2e3)+_0x58d35a(0x1d5)]===_0x58d35a(0x1e6))_0x5d7cad[_0x58d35a(0x2e3)+_0x58d35a(0x1d5)]();}catch{}await new Promise(_0x51020c=>{const _0x15b241=_0x58d35a;_0x259aa7[_0x15b241(0x1fd)](()=>_0x51020c());});}export async function stopWebServer(){const _0x5bddf3=_0x406ff9,_0x16551a=_0x5471aa;stopRequested=!![];bindRetryTimer&&(clearTimeout(bindRetryTimer),bindRetryTimer=null);if(wsServerRef){try{for(const _0x319b0f of wsServerRef[_0x5bddf3(0x15f)]){try{_0x319b0f[_0x5bddf3(0x2c5)]();}catch{}}await new Promise(_0x445230=>wsServerRef[_0x5bddf3(0x1fd)](()=>_0x445230()));}catch{}wsServerRef=null;}if(currentServer){try{await closeHttpServerGracefully(currentServer);}catch{}currentServer=null;}}export function getWebPort(){return actualWebPort;}
|