@frumu/tandem-panel 0.4.14 → 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 +25 -1
- package/README.md +17 -8
- package/bin/cli.js +3 -1
- package/bin/setup.js +260 -9
- 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 +198 -0
- package/server/routes/control-panel-config.js +106 -0
- package/server/routes/swarm.js +18 -1
- package/src/generated/agent-catalog.json +2254 -0
- package/dist/assets/index-DJVNgAiY.css +0 -1
- package/dist/assets/index-xmcHHgpI.js +0 -2460
- 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
|
|
@@ -70,3 +70,27 @@ TANDEM_SEARCH_TIMEOUT_MS=10000
|
|
|
70
70
|
|
|
71
71
|
# Session TTL in minutes
|
|
72
72
|
TANDEM_CONTROL_PANEL_SESSION_TTL_MINUTES=1440
|
|
73
|
+
|
|
74
|
+
# ─── ACA Integration ─────────────────────────────────────────────────
|
|
75
|
+
# Optional external ACA base URL. Leave blank to disable ACA integration.
|
|
76
|
+
# When set, the control panel probes this URL on startup and exposes it via
|
|
77
|
+
# /api/capabilities as aca_integration.
|
|
78
|
+
# Example: ACA_BASE_URL=http://127.0.0.1:39735
|
|
79
|
+
ACA_BASE_URL=
|
|
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
|
+
|
|
88
|
+
# Path on ACA_BASE_URL to use for health/status probe (default: /health)
|
|
89
|
+
ACA_HEALTH_PATH=/health
|
|
90
|
+
|
|
91
|
+
# Probe timeout in milliseconds. Probes that take longer return "aca_probe_timeout".
|
|
92
|
+
ACA_PROBE_TIMEOUT_MS=5000
|
|
93
|
+
|
|
94
|
+
# How long to cache capability probe results, in milliseconds (default: 45000 = 45 s).
|
|
95
|
+
# Set lower for faster ACA availability detection, higher to reduce probe frequency.
|
|
96
|
+
ACA_CAPABILITY_CACHE_TTL_MS=45000
|
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,7 +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 {
|
|
15
|
+
import {
|
|
16
|
+
readControlPanelConfig,
|
|
17
|
+
resolveControlPanelConfigPath,
|
|
18
|
+
resolveControlPanelMode,
|
|
19
|
+
summarizeControlPanelConfig,
|
|
20
|
+
} from "../lib/setup/control-panel-config.js";
|
|
21
|
+
import { createSwarmApiHandler, getOrchestratorMetrics } from "../server/routes/swarm.js";
|
|
22
|
+
import { createAcaApiHandler } from "../server/routes/aca.js";
|
|
23
|
+
import { createCapabilitiesHandler, getCapabilitiesMetrics } from "../server/routes/capabilities.js";
|
|
24
|
+
import { createControlPanelConfigHandler } from "../server/routes/control-panel-config.js";
|
|
16
25
|
|
|
17
26
|
function parseDotEnv(content) {
|
|
18
27
|
const out = {};
|
|
@@ -177,6 +186,11 @@ const ENGINE_PORT = Number.parseInt(process.env.TANDEM_ENGINE_PORT || "39731", 1
|
|
|
177
186
|
const ENGINE_URL = (
|
|
178
187
|
process.env.TANDEM_ENGINE_URL || `http://${ENGINE_HOST}:${ENGINE_PORT}`
|
|
179
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();
|
|
180
194
|
const DEFAULT_TANDEM_SEARCH_URL = (
|
|
181
195
|
process.env.TANDEM_SEARCH_URL || "https://search.tandem.frumu.ai"
|
|
182
196
|
).replace(/\/+$/, "");
|
|
@@ -188,11 +202,20 @@ const SWARM_HIDDEN_RUNS_PATH = resolve(
|
|
|
188
202
|
"swarm-hidden-runs.json"
|
|
189
203
|
);
|
|
190
204
|
const AUTO_START_ENGINE = (process.env.TANDEM_CONTROL_PANEL_AUTO_START_ENGINE || "1") !== "0";
|
|
191
|
-
const CONFIGURED_ENGINE_TOKEN = (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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();
|
|
196
219
|
const SESSION_TTL_MS =
|
|
197
220
|
Number.parseInt(process.env.TANDEM_CONTROL_PANEL_SESSION_TTL_MINUTES || "1440", 10) * 60 * 1000;
|
|
198
221
|
const FILES_ROOT = resolve(
|
|
@@ -1061,7 +1084,9 @@ function readManagedSearchSettings() {
|
|
|
1061
1084
|
has_brave_key: !!String(
|
|
1062
1085
|
env.TANDEM_BRAVE_SEARCH_API_KEY || env.BRAVE_SEARCH_API_KEY || ""
|
|
1063
1086
|
).trim(),
|
|
1064
|
-
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(),
|
|
1065
1090
|
},
|
|
1066
1091
|
reason: localEngine
|
|
1067
1092
|
? ""
|
|
@@ -1102,9 +1127,13 @@ async function writeManagedSearchSettings(payload = {}) {
|
|
|
1102
1127
|
}
|
|
1103
1128
|
|
|
1104
1129
|
const exaKey = String(payload.exa_api_key || payload.exaApiKey || "").trim();
|
|
1105
|
-
if (exaKey)
|
|
1130
|
+
if (exaKey) {
|
|
1131
|
+
nextEnv.TANDEM_EXA_API_KEY = exaKey;
|
|
1132
|
+
delete nextEnv.TANDEM_EXA_SEARCH_API_KEY;
|
|
1133
|
+
}
|
|
1106
1134
|
else if (payload.clear_exa_key || payload.clearExaKey) {
|
|
1107
1135
|
delete nextEnv.TANDEM_EXA_API_KEY;
|
|
1136
|
+
delete nextEnv.TANDEM_EXA_SEARCH_API_KEY;
|
|
1108
1137
|
delete nextEnv.EXA_API_KEY;
|
|
1109
1138
|
}
|
|
1110
1139
|
|
|
@@ -1142,6 +1171,54 @@ function sendJson(res, code, payload) {
|
|
|
1142
1171
|
res.end(body);
|
|
1143
1172
|
}
|
|
1144
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
|
+
|
|
1145
1222
|
function pushSwarmEvent(kind, payload = {}) {
|
|
1146
1223
|
const event = {
|
|
1147
1224
|
kind,
|
|
@@ -1187,6 +1264,73 @@ async function engineHealth(token = "") {
|
|
|
1187
1264
|
}
|
|
1188
1265
|
}
|
|
1189
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
|
+
|
|
1190
1334
|
async function validateEngineToken(token) {
|
|
1191
1335
|
try {
|
|
1192
1336
|
const response = await fetch(`${ENGINE_URL}/config/providers`, {
|
|
@@ -1224,7 +1368,7 @@ async function ensureEngineRunning() {
|
|
|
1224
1368
|
engineEntrypoint = require.resolve("@frumu/tandem/bin/tandem-engine.js");
|
|
1225
1369
|
} catch (e) {
|
|
1226
1370
|
err("Could not resolve @frumu/tandem binary entrypoint.");
|
|
1227
|
-
err("Reinstall with: npm i -g @frumu/tandem
|
|
1371
|
+
err("Reinstall with: npm i -g @frumu/tandem");
|
|
1228
1372
|
throw e;
|
|
1229
1373
|
}
|
|
1230
1374
|
|
|
@@ -4519,6 +4663,38 @@ const handleSwarmApi = createSwarmApiHandler({
|
|
|
4519
4663
|
setActiveSwarmRunId,
|
|
4520
4664
|
});
|
|
4521
4665
|
|
|
4666
|
+
const handleCapabilities = createCapabilitiesHandler({
|
|
4667
|
+
PROBE_TIMEOUT_MS: Number.parseInt(process.env.ACA_PROBE_TIMEOUT_MS || "5000", 10),
|
|
4668
|
+
ACA_BASE_URL,
|
|
4669
|
+
ACA_HEALTH_PATH: process.env.ACA_HEALTH_PATH || "/health",
|
|
4670
|
+
getAcaToken,
|
|
4671
|
+
getInstallProfile,
|
|
4672
|
+
engineHealth: async (token) => {
|
|
4673
|
+
const health = await engineHealth(token).catch(() => null);
|
|
4674
|
+
return health;
|
|
4675
|
+
},
|
|
4676
|
+
sendJson,
|
|
4677
|
+
cacheTtlMs: Number.parseInt(process.env.ACA_CAPABILITY_CACHE_TTL_MS || "45000", 10),
|
|
4678
|
+
});
|
|
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
|
+
|
|
4522
4698
|
async function handleApi(req, res) {
|
|
4523
4699
|
const pathname = new URL(req.url, `http://127.0.0.1:${PORTAL_PORT}`).pathname;
|
|
4524
4700
|
|
|
@@ -4534,6 +4710,26 @@ async function handleApi(req, res) {
|
|
|
4534
4710
|
return true;
|
|
4535
4711
|
}
|
|
4536
4712
|
|
|
4713
|
+
if (pathname === "/api/capabilities" && req.method === "GET") {
|
|
4714
|
+
await handleCapabilities(req, res);
|
|
4715
|
+
return true;
|
|
4716
|
+
}
|
|
4717
|
+
|
|
4718
|
+
if (pathname === "/api/capabilities/metrics" && req.method === "GET") {
|
|
4719
|
+
sendJson(res, 200, getCapabilitiesMetrics());
|
|
4720
|
+
return true;
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
if (pathname === "/api/install/profile" && req.method === "GET") {
|
|
4724
|
+
await handleCapabilities(req, res);
|
|
4725
|
+
return true;
|
|
4726
|
+
}
|
|
4727
|
+
|
|
4728
|
+
if (pathname === "/api/system/orchestrator-metrics" && req.method === "GET") {
|
|
4729
|
+
sendJson(res, 200, getOrchestratorMetrics());
|
|
4730
|
+
return true;
|
|
4731
|
+
}
|
|
4732
|
+
|
|
4537
4733
|
if (pathname === "/api/system/search-settings" && req.method === "GET") {
|
|
4538
4734
|
const session = requireSession(req, res);
|
|
4539
4735
|
if (!session) return true;
|
|
@@ -4557,6 +4753,49 @@ async function handleApi(req, res) {
|
|
|
4557
4753
|
return true;
|
|
4558
4754
|
}
|
|
4559
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
|
+
|
|
4560
4799
|
if (pathname === "/api/auth/login" && req.method === "POST") {
|
|
4561
4800
|
await handleAuthLogin(req, res);
|
|
4562
4801
|
return true;
|
|
@@ -4592,12 +4831,24 @@ async function handleApi(req, res) {
|
|
|
4592
4831
|
return true;
|
|
4593
4832
|
}
|
|
4594
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
|
+
|
|
4595
4840
|
if (pathname.startsWith("/api/swarm") || pathname.startsWith("/api/orchestrator")) {
|
|
4596
4841
|
const session = requireSession(req, res);
|
|
4597
4842
|
if (!session) return true;
|
|
4598
4843
|
return handleSwarmApi(req, res, session);
|
|
4599
4844
|
}
|
|
4600
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
|
+
|
|
4601
4852
|
if (pathname.startsWith("/api/files")) {
|
|
4602
4853
|
const session = requireSession(req, res);
|
|
4603
4854
|
if (!session) return true;
|