@frumu/tandem-panel 0.4.15 → 0.4.16
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/.env.example +8 -1
- package/README.md +17 -8
- package/bin/cli.js +3 -1
- package/bin/setup.js +233 -10
- package/dist/assets/index-C3pGFPtZ.css +1 -0
- package/dist/assets/index-fje-pNlH.js +2474 -0
- package/dist/assets/{motion-BCvrfAt1.js → motion-m8lxAefi.js} +1 -1
- package/dist/assets/preact-vendor-CWXGD9A4.js +1 -0
- package/dist/assets/{react-query-wD0mx2Xi.js → react-query-tz3oGfUr.js} +1 -1
- package/dist/assets/vendor-Bw8uHUnC.js +180 -0
- package/dist/index.html +6 -6
- package/lib/setup/control-panel-config.js +196 -0
- package/package.json +9 -3
- package/server/routes/aca.js +97 -0
- package/server/routes/capabilities.js +51 -12
- package/server/routes/control-panel-config.js +106 -0
- package/src/generated/agent-catalog.json +2254 -0
- package/dist/assets/index-DAtDe1Vc.js +0 -2460
- package/dist/assets/index-DzX1-UXX.css +0 -1
- package/dist/assets/preact-vendor-jo0muZ28.js +0 -1
- package/dist/assets/vendor-BB3fzNns.js +0 -180
package/.env.example
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Control panel bind port
|
|
2
2
|
TANDEM_CONTROL_PANEL_PORT=39732
|
|
3
3
|
|
|
4
|
-
# Tip: run `tandem
|
|
4
|
+
# Tip: run `tandem panel init` to auto-generate this file and token.
|
|
5
5
|
|
|
6
6
|
# Control panel bind host (loopback by default)
|
|
7
7
|
TANDEM_CONTROL_PANEL_HOST=127.0.0.1
|
|
@@ -78,6 +78,13 @@ TANDEM_CONTROL_PANEL_SESSION_TTL_MINUTES=1440
|
|
|
78
78
|
# Example: ACA_BASE_URL=http://127.0.0.1:39735
|
|
79
79
|
ACA_BASE_URL=
|
|
80
80
|
|
|
81
|
+
# ACA bearer token. Required for ACA project, run, and log APIs.
|
|
82
|
+
# If both ACA_API_TOKEN and ACA_API_TOKEN_FILE are set, ACA_API_TOKEN wins.
|
|
83
|
+
ACA_API_TOKEN=
|
|
84
|
+
|
|
85
|
+
# Optional path to a file containing the ACA bearer token.
|
|
86
|
+
ACA_API_TOKEN_FILE=
|
|
87
|
+
|
|
81
88
|
# Path on ACA_BASE_URL to use for health/status probe (default: /health)
|
|
82
89
|
ACA_HEALTH_PATH=/health
|
|
83
90
|
|
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Full web control center for Tandem Engine (non-desktop entry point).
|
|
4
4
|
|
|
5
|
+
This package remains the control-panel add-on during migration. The canonical
|
|
6
|
+
master CLI now lives in `@frumu/tandem` as `tandem`, and this package keeps
|
|
7
|
+
`tandem-setup` / `tandem-control-panel` as compatibility shims.
|
|
8
|
+
|
|
5
9
|
## Install
|
|
6
10
|
|
|
7
11
|
```bash
|
|
@@ -41,7 +45,9 @@ Use the scaffold when you want the actual app source in your own folder so you c
|
|
|
41
45
|
## Official Bootstrap
|
|
42
46
|
|
|
43
47
|
```bash
|
|
44
|
-
|
|
48
|
+
npm i -g @frumu/tandem
|
|
49
|
+
tandem install panel
|
|
50
|
+
tandem panel init
|
|
45
51
|
```
|
|
46
52
|
|
|
47
53
|
This creates a canonical env file, bootstraps engine state, and installs services on Linux/macOS when run with the privileges needed for service registration.
|
|
@@ -49,10 +55,12 @@ This creates a canonical env file, bootstraps engine state, and installs service
|
|
|
49
55
|
Useful follow-up commands:
|
|
50
56
|
|
|
51
57
|
```bash
|
|
52
|
-
tandem
|
|
53
|
-
tandem
|
|
54
|
-
tandem
|
|
55
|
-
tandem
|
|
58
|
+
tandem doctor
|
|
59
|
+
tandem status
|
|
60
|
+
tandem service status
|
|
61
|
+
tandem service restart
|
|
62
|
+
tandem panel doctor
|
|
63
|
+
tandem panel open
|
|
56
64
|
```
|
|
57
65
|
|
|
58
66
|
## Run Foreground
|
|
@@ -76,7 +84,8 @@ tandem-setup service restart
|
|
|
76
84
|
tandem-setup service logs
|
|
77
85
|
```
|
|
78
86
|
|
|
79
|
-
Legacy flag mode is still supported for compatibility
|
|
87
|
+
Legacy flag mode is still supported for compatibility, but new installs should
|
|
88
|
+
prefer `tandem` and the panel add-on commands:
|
|
80
89
|
|
|
81
90
|
`tandem-control-panel --init`, `--install-services`, and `--service-op=...`
|
|
82
91
|
|
|
@@ -168,8 +177,8 @@ Notes:
|
|
|
168
177
|
|
|
169
178
|
## Setup Flow
|
|
170
179
|
|
|
171
|
-
1. Run `tandem-setup init`.
|
|
172
|
-
2. Verify with `tandem-setup doctor`.
|
|
180
|
+
1. Run `tandem panel init` or `tandem-setup init`.
|
|
181
|
+
2. Verify with `tandem panel doctor` or `tandem-setup doctor`.
|
|
173
182
|
3. If running foreground, start `tandem-control-panel`.
|
|
174
183
|
4. Sign in with the printed `TANDEM_CONTROL_PANEL_ENGINE_TOKEN`.
|
|
175
184
|
|
package/bin/cli.js
CHANGED
|
@@ -57,7 +57,9 @@ async function main() {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
if (first.startsWith("--")) {
|
|
60
|
-
console.warn(
|
|
60
|
+
console.warn(
|
|
61
|
+
"[Tandem Setup] Legacy flag mode is deprecated. Use `tandem panel init|service|doctor`."
|
|
62
|
+
);
|
|
61
63
|
process.exit(await runLegacy(argv));
|
|
62
64
|
}
|
|
63
65
|
|
package/bin/setup.js
CHANGED
|
@@ -12,8 +12,16 @@ import { fileURLToPath } from "url";
|
|
|
12
12
|
import { createRequire } from "module";
|
|
13
13
|
import { homedir } from "os";
|
|
14
14
|
import { ensureBootstrapEnv, resolveEnvLoadOrder } from "../lib/setup/env.js";
|
|
15
|
+
import {
|
|
16
|
+
readControlPanelConfig,
|
|
17
|
+
resolveControlPanelConfigPath,
|
|
18
|
+
resolveControlPanelMode,
|
|
19
|
+
summarizeControlPanelConfig,
|
|
20
|
+
} from "../lib/setup/control-panel-config.js";
|
|
15
21
|
import { createSwarmApiHandler, getOrchestratorMetrics } from "../server/routes/swarm.js";
|
|
22
|
+
import { createAcaApiHandler } from "../server/routes/aca.js";
|
|
16
23
|
import { createCapabilitiesHandler, getCapabilitiesMetrics } from "../server/routes/capabilities.js";
|
|
24
|
+
import { createControlPanelConfigHandler } from "../server/routes/control-panel-config.js";
|
|
17
25
|
|
|
18
26
|
function parseDotEnv(content) {
|
|
19
27
|
const out = {};
|
|
@@ -178,6 +186,11 @@ const ENGINE_PORT = Number.parseInt(process.env.TANDEM_ENGINE_PORT || "39731", 1
|
|
|
178
186
|
const ENGINE_URL = (
|
|
179
187
|
process.env.TANDEM_ENGINE_URL || `http://${ENGINE_HOST}:${ENGINE_PORT}`
|
|
180
188
|
).replace(/\/+$/, "");
|
|
189
|
+
const ACA_BASE_URL = String(process.env.ACA_BASE_URL || "")
|
|
190
|
+
.trim()
|
|
191
|
+
.replace(/\/+$/, "");
|
|
192
|
+
const CONTROL_PANEL_CONFIG_FILE = String(process.env.TANDEM_CONTROL_PANEL_CONFIG_FILE || "").trim();
|
|
193
|
+
const CONTROL_PANEL_MODE = String(process.env.TANDEM_CONTROL_PANEL_MODE || "auto").trim();
|
|
181
194
|
const DEFAULT_TANDEM_SEARCH_URL = (
|
|
182
195
|
process.env.TANDEM_SEARCH_URL || "https://search.tandem.frumu.ai"
|
|
183
196
|
).replace(/\/+$/, "");
|
|
@@ -189,11 +202,20 @@ const SWARM_HIDDEN_RUNS_PATH = resolve(
|
|
|
189
202
|
"swarm-hidden-runs.json"
|
|
190
203
|
);
|
|
191
204
|
const AUTO_START_ENGINE = (process.env.TANDEM_CONTROL_PANEL_AUTO_START_ENGINE || "1") !== "0";
|
|
192
|
-
const CONFIGURED_ENGINE_TOKEN = (
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
205
|
+
const CONFIGURED_ENGINE_TOKEN = (() => {
|
|
206
|
+
const explicit = String(
|
|
207
|
+
process.env.TANDEM_CONTROL_PANEL_ENGINE_TOKEN || process.env.TANDEM_API_TOKEN || ""
|
|
208
|
+
).trim();
|
|
209
|
+
if (explicit) return explicit;
|
|
210
|
+
const tokenFile = String(process.env.TANDEM_API_TOKEN_FILE || "").trim();
|
|
211
|
+
if (tokenFile) {
|
|
212
|
+
try {
|
|
213
|
+
return readFileSync(resolve(tokenFile), "utf8").trim();
|
|
214
|
+
} catch {}
|
|
215
|
+
}
|
|
216
|
+
return "";
|
|
217
|
+
})();
|
|
218
|
+
const ACA_TOKEN_FILE = String(process.env.ACA_API_TOKEN_FILE || "").trim();
|
|
197
219
|
const SESSION_TTL_MS =
|
|
198
220
|
Number.parseInt(process.env.TANDEM_CONTROL_PANEL_SESSION_TTL_MINUTES || "1440", 10) * 60 * 1000;
|
|
199
221
|
const FILES_ROOT = resolve(
|
|
@@ -1062,7 +1084,9 @@ function readManagedSearchSettings() {
|
|
|
1062
1084
|
has_brave_key: !!String(
|
|
1063
1085
|
env.TANDEM_BRAVE_SEARCH_API_KEY || env.BRAVE_SEARCH_API_KEY || ""
|
|
1064
1086
|
).trim(),
|
|
1065
|
-
has_exa_key: !!String(
|
|
1087
|
+
has_exa_key: !!String(
|
|
1088
|
+
env.TANDEM_EXA_API_KEY || env.TANDEM_EXA_SEARCH_API_KEY || env.EXA_API_KEY || ""
|
|
1089
|
+
).trim(),
|
|
1066
1090
|
},
|
|
1067
1091
|
reason: localEngine
|
|
1068
1092
|
? ""
|
|
@@ -1103,9 +1127,13 @@ async function writeManagedSearchSettings(payload = {}) {
|
|
|
1103
1127
|
}
|
|
1104
1128
|
|
|
1105
1129
|
const exaKey = String(payload.exa_api_key || payload.exaApiKey || "").trim();
|
|
1106
|
-
if (exaKey)
|
|
1130
|
+
if (exaKey) {
|
|
1131
|
+
nextEnv.TANDEM_EXA_API_KEY = exaKey;
|
|
1132
|
+
delete nextEnv.TANDEM_EXA_SEARCH_API_KEY;
|
|
1133
|
+
}
|
|
1107
1134
|
else if (payload.clear_exa_key || payload.clearExaKey) {
|
|
1108
1135
|
delete nextEnv.TANDEM_EXA_API_KEY;
|
|
1136
|
+
delete nextEnv.TANDEM_EXA_SEARCH_API_KEY;
|
|
1109
1137
|
delete nextEnv.EXA_API_KEY;
|
|
1110
1138
|
}
|
|
1111
1139
|
|
|
@@ -1143,6 +1171,54 @@ function sendJson(res, code, payload) {
|
|
|
1143
1171
|
res.end(body);
|
|
1144
1172
|
}
|
|
1145
1173
|
|
|
1174
|
+
function readOptionalTokenFile(pathname) {
|
|
1175
|
+
const target = String(pathname || "").trim();
|
|
1176
|
+
if (!target) return "";
|
|
1177
|
+
try {
|
|
1178
|
+
return readFileSync(resolve(target), "utf8").trim();
|
|
1179
|
+
} catch {
|
|
1180
|
+
return "";
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
function getAcaToken() {
|
|
1185
|
+
return (
|
|
1186
|
+
String(process.env.ACA_API_TOKEN || "").trim() ||
|
|
1187
|
+
readOptionalTokenFile(ACA_TOKEN_FILE) ||
|
|
1188
|
+
""
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
function getControlPanelConfigPath() {
|
|
1193
|
+
return resolveControlPanelConfigPath({
|
|
1194
|
+
explicitPath: CONTROL_PANEL_CONFIG_FILE,
|
|
1195
|
+
stateDir: process.env.TANDEM_CONTROL_PANEL_STATE_DIR,
|
|
1196
|
+
env: process.env,
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
async function getInstallProfile({ acaAvailable = false, acaReason = "" } = {}) {
|
|
1201
|
+
const configPath = getControlPanelConfigPath();
|
|
1202
|
+
const config = readControlPanelConfig(configPath);
|
|
1203
|
+
const mode = resolveControlPanelMode({
|
|
1204
|
+
config,
|
|
1205
|
+
envMode: CONTROL_PANEL_MODE,
|
|
1206
|
+
acaAvailable,
|
|
1207
|
+
});
|
|
1208
|
+
const summary = summarizeControlPanelConfig(config);
|
|
1209
|
+
return {
|
|
1210
|
+
control_panel_mode: mode.mode,
|
|
1211
|
+
control_panel_mode_source: mode.source,
|
|
1212
|
+
control_panel_mode_reason: mode.reason || "",
|
|
1213
|
+
control_panel_config_path: configPath,
|
|
1214
|
+
control_panel_config_ready: summary.ready,
|
|
1215
|
+
control_panel_config_missing: summary.missing,
|
|
1216
|
+
control_panel_compact_nav: !!summary.control_panel?.aca_compact_nav,
|
|
1217
|
+
aca_integration: !!acaAvailable,
|
|
1218
|
+
aca_reason: acaReason || "",
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1146
1222
|
function pushSwarmEvent(kind, payload = {}) {
|
|
1147
1223
|
const event = {
|
|
1148
1224
|
kind,
|
|
@@ -1188,6 +1264,73 @@ async function engineHealth(token = "") {
|
|
|
1188
1264
|
}
|
|
1189
1265
|
}
|
|
1190
1266
|
|
|
1267
|
+
async function executeEngineTool(token, tool, args = {}) {
|
|
1268
|
+
const response = await fetch(`${ENGINE_URL}/tool/execute`, {
|
|
1269
|
+
method: "POST",
|
|
1270
|
+
headers: {
|
|
1271
|
+
"content-type": "application/json",
|
|
1272
|
+
authorization: `Bearer ${token}`,
|
|
1273
|
+
"x-tandem-token": token,
|
|
1274
|
+
},
|
|
1275
|
+
body: JSON.stringify({ tool, args }),
|
|
1276
|
+
signal: AbortSignal.timeout(15000),
|
|
1277
|
+
});
|
|
1278
|
+
const text = await response.text().catch(() => "");
|
|
1279
|
+
let parsed = null;
|
|
1280
|
+
try {
|
|
1281
|
+
parsed = text ? JSON.parse(text) : {};
|
|
1282
|
+
} catch {
|
|
1283
|
+
parsed = null;
|
|
1284
|
+
}
|
|
1285
|
+
if (!response.ok) {
|
|
1286
|
+
const message =
|
|
1287
|
+
parsed?.error || parsed?.detail || text || `${tool} failed (${response.status})`;
|
|
1288
|
+
const error = new Error(message);
|
|
1289
|
+
error.statusCode = response.status;
|
|
1290
|
+
error.payload = parsed;
|
|
1291
|
+
throw error;
|
|
1292
|
+
}
|
|
1293
|
+
return parsed || {};
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function buildSearchTestMarkdown(payload) {
|
|
1297
|
+
const query = String(payload?.query || "").trim();
|
|
1298
|
+
const backend = String(payload?.backend || "unknown").trim();
|
|
1299
|
+
const configuredBackend = String(payload?.configured_backend || backend || "unknown").trim();
|
|
1300
|
+
const attemptedBackends = Array.isArray(payload?.attempted_backends)
|
|
1301
|
+
? payload.attempted_backends.filter(Boolean)
|
|
1302
|
+
: [];
|
|
1303
|
+
const resultCount = Number(payload?.result_count || 0) || 0;
|
|
1304
|
+
const partial = payload?.partial === true;
|
|
1305
|
+
const results = Array.isArray(payload?.results) ? payload.results : [];
|
|
1306
|
+
|
|
1307
|
+
const lines = [
|
|
1308
|
+
"# Websearch test",
|
|
1309
|
+
"",
|
|
1310
|
+
`- Query: \`${query || "n/a"}\``,
|
|
1311
|
+
`- Backend used: \`${backend || "unknown"}\``,
|
|
1312
|
+
`- Configured backend: \`${configuredBackend || "unknown"}\``,
|
|
1313
|
+
`- Attempted backends: ${attemptedBackends.length ? attemptedBackends.map((name) => `\`${name}\``).join(", ") : "none"}`,
|
|
1314
|
+
`- Results: ${resultCount}${partial ? " (partial)" : ""}`,
|
|
1315
|
+
"",
|
|
1316
|
+
];
|
|
1317
|
+
|
|
1318
|
+
if (!results.length) {
|
|
1319
|
+
lines.push("No search results were returned.");
|
|
1320
|
+
return lines.join("\n");
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
lines.push("## Top results", "");
|
|
1324
|
+
for (const [index, row] of results.entries()) {
|
|
1325
|
+
const title = String(row?.title || row?.url || `Result ${index + 1}`).trim();
|
|
1326
|
+
const url = String(row?.url || "").trim();
|
|
1327
|
+
const snippet = String(row?.snippet || "").trim();
|
|
1328
|
+
lines.push(`${index + 1}. [${title}](${url || "#"})`);
|
|
1329
|
+
if (snippet) lines.push(` ${snippet}`);
|
|
1330
|
+
}
|
|
1331
|
+
return lines.join("\n");
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1191
1334
|
async function validateEngineToken(token) {
|
|
1192
1335
|
try {
|
|
1193
1336
|
const response = await fetch(`${ENGINE_URL}/config/providers`, {
|
|
@@ -1225,7 +1368,7 @@ async function ensureEngineRunning() {
|
|
|
1225
1368
|
engineEntrypoint = require.resolve("@frumu/tandem/bin/tandem-engine.js");
|
|
1226
1369
|
} catch (e) {
|
|
1227
1370
|
err("Could not resolve @frumu/tandem binary entrypoint.");
|
|
1228
|
-
err("Reinstall with: npm i -g @frumu/tandem
|
|
1371
|
+
err("Reinstall with: npm i -g @frumu/tandem");
|
|
1229
1372
|
throw e;
|
|
1230
1373
|
}
|
|
1231
1374
|
|
|
@@ -4522,8 +4665,10 @@ const handleSwarmApi = createSwarmApiHandler({
|
|
|
4522
4665
|
|
|
4523
4666
|
const handleCapabilities = createCapabilitiesHandler({
|
|
4524
4667
|
PROBE_TIMEOUT_MS: Number.parseInt(process.env.ACA_PROBE_TIMEOUT_MS || "5000", 10),
|
|
4525
|
-
ACA_BASE_URL
|
|
4526
|
-
ACA_HEALTH_PATH: "/health",
|
|
4668
|
+
ACA_BASE_URL,
|
|
4669
|
+
ACA_HEALTH_PATH: process.env.ACA_HEALTH_PATH || "/health",
|
|
4670
|
+
getAcaToken,
|
|
4671
|
+
getInstallProfile,
|
|
4527
4672
|
engineHealth: async (token) => {
|
|
4528
4673
|
const health = await engineHealth(token).catch(() => null);
|
|
4529
4674
|
return health;
|
|
@@ -4532,6 +4677,24 @@ const handleCapabilities = createCapabilitiesHandler({
|
|
|
4532
4677
|
cacheTtlMs: Number.parseInt(process.env.ACA_CAPABILITY_CACHE_TTL_MS || "45000", 10),
|
|
4533
4678
|
});
|
|
4534
4679
|
|
|
4680
|
+
const handleAcaApi = createAcaApiHandler({
|
|
4681
|
+
PORTAL_PORT,
|
|
4682
|
+
ACA_BASE_URL,
|
|
4683
|
+
getAcaToken,
|
|
4684
|
+
sendJson,
|
|
4685
|
+
});
|
|
4686
|
+
|
|
4687
|
+
const handleControlPanelConfig = createControlPanelConfigHandler({
|
|
4688
|
+
CONTROL_PANEL_CONFIG_FILE,
|
|
4689
|
+
TANDEM_CONTROL_PANEL_STATE_DIR: process.env.TANDEM_CONTROL_PANEL_STATE_DIR || "",
|
|
4690
|
+
CONTROL_PANEL_MODE,
|
|
4691
|
+
ACA_BASE_URL,
|
|
4692
|
+
PROBE_TIMEOUT_MS: Number.parseInt(process.env.ACA_PROBE_TIMEOUT_MS || "5000", 10),
|
|
4693
|
+
getAcaToken,
|
|
4694
|
+
sendJson,
|
|
4695
|
+
readJsonBody,
|
|
4696
|
+
});
|
|
4697
|
+
|
|
4535
4698
|
async function handleApi(req, res) {
|
|
4536
4699
|
const pathname = new URL(req.url, `http://127.0.0.1:${PORTAL_PORT}`).pathname;
|
|
4537
4700
|
|
|
@@ -4557,6 +4720,11 @@ async function handleApi(req, res) {
|
|
|
4557
4720
|
return true;
|
|
4558
4721
|
}
|
|
4559
4722
|
|
|
4723
|
+
if (pathname === "/api/install/profile" && req.method === "GET") {
|
|
4724
|
+
await handleCapabilities(req, res);
|
|
4725
|
+
return true;
|
|
4726
|
+
}
|
|
4727
|
+
|
|
4560
4728
|
if (pathname === "/api/system/orchestrator-metrics" && req.method === "GET") {
|
|
4561
4729
|
sendJson(res, 200, getOrchestratorMetrics());
|
|
4562
4730
|
return true;
|
|
@@ -4585,6 +4753,49 @@ async function handleApi(req, res) {
|
|
|
4585
4753
|
return true;
|
|
4586
4754
|
}
|
|
4587
4755
|
|
|
4756
|
+
if (pathname === "/api/system/search-settings/test" && req.method === "POST") {
|
|
4757
|
+
const session = requireSession(req, res);
|
|
4758
|
+
if (!session) return true;
|
|
4759
|
+
try {
|
|
4760
|
+
const payload = await readJsonBody(req);
|
|
4761
|
+
const query = String(payload?.query || "").trim();
|
|
4762
|
+
const limitRaw = Number.parseInt(String(payload?.limit || "5"), 10);
|
|
4763
|
+
const limit = Number.isFinite(limitRaw) ? Math.min(Math.max(limitRaw, 1), 10) : 5;
|
|
4764
|
+
if (!query) {
|
|
4765
|
+
sendJson(res, 400, { ok: false, error: "Search query is required." });
|
|
4766
|
+
return true;
|
|
4767
|
+
}
|
|
4768
|
+
const result = await executeEngineTool(session.token, "websearch", {
|
|
4769
|
+
query,
|
|
4770
|
+
limit,
|
|
4771
|
+
});
|
|
4772
|
+
const output = String(result?.output || "");
|
|
4773
|
+
let parsedOutput = null;
|
|
4774
|
+
try {
|
|
4775
|
+
parsedOutput = output ? JSON.parse(output) : null;
|
|
4776
|
+
} catch {
|
|
4777
|
+
parsedOutput = null;
|
|
4778
|
+
}
|
|
4779
|
+
const markdown = parsedOutput
|
|
4780
|
+
? buildSearchTestMarkdown(parsedOutput)
|
|
4781
|
+
: `# Websearch test\n\n## Output\n\n\`\`\`\n${output || "No output returned."}\n\`\`\``;
|
|
4782
|
+
sendJson(res, 200, {
|
|
4783
|
+
ok: true,
|
|
4784
|
+
query,
|
|
4785
|
+
markdown,
|
|
4786
|
+
output,
|
|
4787
|
+
parsed_output: parsedOutput,
|
|
4788
|
+
metadata: result?.metadata || {},
|
|
4789
|
+
});
|
|
4790
|
+
} catch (error) {
|
|
4791
|
+
sendJson(res, Number(error?.statusCode || 500), {
|
|
4792
|
+
ok: false,
|
|
4793
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4794
|
+
});
|
|
4795
|
+
}
|
|
4796
|
+
return true;
|
|
4797
|
+
}
|
|
4798
|
+
|
|
4588
4799
|
if (pathname === "/api/auth/login" && req.method === "POST") {
|
|
4589
4800
|
await handleAuthLogin(req, res);
|
|
4590
4801
|
return true;
|
|
@@ -4620,12 +4831,24 @@ async function handleApi(req, res) {
|
|
|
4620
4831
|
return true;
|
|
4621
4832
|
}
|
|
4622
4833
|
|
|
4834
|
+
if (pathname === "/api/control-panel/config" && (req.method === "GET" || req.method === "PATCH")) {
|
|
4835
|
+
const session = requireSession(req, res);
|
|
4836
|
+
if (!session) return true;
|
|
4837
|
+
return handleControlPanelConfig(req, res);
|
|
4838
|
+
}
|
|
4839
|
+
|
|
4623
4840
|
if (pathname.startsWith("/api/swarm") || pathname.startsWith("/api/orchestrator")) {
|
|
4624
4841
|
const session = requireSession(req, res);
|
|
4625
4842
|
if (!session) return true;
|
|
4626
4843
|
return handleSwarmApi(req, res, session);
|
|
4627
4844
|
}
|
|
4628
4845
|
|
|
4846
|
+
if (pathname.startsWith("/api/aca")) {
|
|
4847
|
+
const session = requireSession(req, res);
|
|
4848
|
+
if (!session) return true;
|
|
4849
|
+
return handleAcaApi(req, res);
|
|
4850
|
+
}
|
|
4851
|
+
|
|
4629
4852
|
if (pathname.startsWith("/api/files")) {
|
|
4630
4853
|
const session = requireSession(req, res);
|
|
4631
4854
|
if (!session) return true;
|