@frumu/tandem-panel 0.4.16 → 0.4.18
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 +2 -2
- package/README.md +10 -0
- package/bin/setup.js +90 -3
- package/dist/assets/index-7omBpQ9G.js +2900 -0
- package/dist/assets/index-B_avj5aY.css +1 -0
- package/dist/assets/{react-query-tz3oGfUr.js → react-query-BiFBqyAt.js} +1 -1
- package/dist/assets/{vendor-Bw8uHUnC.js → vendor-Q0KoFXrG.js} +2 -2
- package/dist/index.html +4 -4
- package/lib/setup/env.js +1 -1
- package/package.json +4 -4
- package/dist/assets/index-C3pGFPtZ.css +0 -1
- package/dist/assets/index-fje-pNlH.js +0 -2474
package/.env.example
CHANGED
|
@@ -31,7 +31,7 @@ TANDEM_CONTROL_PANEL_ENGINE_TOKEN=tk_change_me
|
|
|
31
31
|
TANDEM_DISABLE_TOOL_GUARD_BUDGETS=1
|
|
32
32
|
TANDEM_TOOL_ROUTER_ENABLED=0
|
|
33
33
|
TANDEM_PROMPT_CONTEXT_HOOK_TIMEOUT_MS=5000
|
|
34
|
-
TANDEM_PROVIDER_STREAM_CONNECT_TIMEOUT_MS=
|
|
34
|
+
TANDEM_PROVIDER_STREAM_CONNECT_TIMEOUT_MS=90000
|
|
35
35
|
TANDEM_PROVIDER_STREAM_IDLE_TIMEOUT_MS=90000
|
|
36
36
|
TANDEM_PERMISSION_WAIT_TIMEOUT_MS=15000
|
|
37
37
|
TANDEM_TOOL_EXEC_TIMEOUT_MS=45000
|
|
@@ -40,7 +40,7 @@ TANDEM_BASH_TIMEOUT_MS=30000
|
|
|
40
40
|
# Built-in websearch defaults. `auto` can try configured backends in order
|
|
41
41
|
# (managed Tandem URL, SearxNG, Brave, Exa) before falling back.
|
|
42
42
|
TANDEM_SEARCH_BACKEND=auto
|
|
43
|
-
TANDEM_SEARCH_URL=https://search.tandem.
|
|
43
|
+
TANDEM_SEARCH_URL=https://search.tandem.ac
|
|
44
44
|
TANDEM_SEARCH_TIMEOUT_MS=10000
|
|
45
45
|
# Optional direct-provider overrides
|
|
46
46
|
# TANDEM_BRAVE_SEARCH_API_KEY=
|
package/README.md
CHANGED
|
@@ -6,6 +6,15 @@ This package remains the control-panel add-on during migration. The canonical
|
|
|
6
6
|
master CLI now lives in `@frumu/tandem` as `tandem`, and this package keeps
|
|
7
7
|
`tandem-setup` / `tandem-control-panel` as compatibility shims.
|
|
8
8
|
|
|
9
|
+
## Fastest Ways To Start
|
|
10
|
+
|
|
11
|
+
- Web control panel: `npm i -g @frumu/tandem && tandem install panel && tandem panel init`
|
|
12
|
+
- Raw local engine: `npm i -g @frumu/tandem && tandem-engine serve --hostname 127.0.0.1 --port 39731`
|
|
13
|
+
- Terminal UI: `npm i -g @frumu/tandem-tui && tandem-tui`
|
|
14
|
+
- SDK usage: `npm install @frumu/tandem-client` or `pip install tandem-client`
|
|
15
|
+
|
|
16
|
+
If you are not sure which path to take, start with the web control panel. It gives you the engine, panel, token setup, and service install flow in one place.
|
|
17
|
+
|
|
9
18
|
## Install
|
|
10
19
|
|
|
11
20
|
```bash
|
|
@@ -57,6 +66,7 @@ Useful follow-up commands:
|
|
|
57
66
|
```bash
|
|
58
67
|
tandem doctor
|
|
59
68
|
tandem status
|
|
69
|
+
tandem service install
|
|
60
70
|
tandem service status
|
|
61
71
|
tandem service restart
|
|
62
72
|
tandem panel doctor
|
package/bin/setup.js
CHANGED
|
@@ -192,7 +192,7 @@ const ACA_BASE_URL = String(process.env.ACA_BASE_URL || "")
|
|
|
192
192
|
const CONTROL_PANEL_CONFIG_FILE = String(process.env.TANDEM_CONTROL_PANEL_CONFIG_FILE || "").trim();
|
|
193
193
|
const CONTROL_PANEL_MODE = String(process.env.TANDEM_CONTROL_PANEL_MODE || "auto").trim();
|
|
194
194
|
const DEFAULT_TANDEM_SEARCH_URL = (
|
|
195
|
-
process.env.TANDEM_SEARCH_URL || "https://search.tandem.
|
|
195
|
+
process.env.TANDEM_SEARCH_URL || "https://search.tandem.ac"
|
|
196
196
|
).replace(/\/+$/, "");
|
|
197
197
|
const SWARM_RUNS_PATH = resolve(homedir(), ".tandem", "control-panel", "swarm-runs.json");
|
|
198
198
|
const SWARM_HIDDEN_RUNS_PATH = resolve(
|
|
@@ -822,7 +822,7 @@ async function installServices() {
|
|
|
822
822
|
TANDEM_PROMPT_CONTEXT_HOOK_TIMEOUT_MS:
|
|
823
823
|
existingEngineEnv.TANDEM_PROMPT_CONTEXT_HOOK_TIMEOUT_MS || "5000",
|
|
824
824
|
TANDEM_PROVIDER_STREAM_CONNECT_TIMEOUT_MS:
|
|
825
|
-
existingEngineEnv.TANDEM_PROVIDER_STREAM_CONNECT_TIMEOUT_MS || "
|
|
825
|
+
existingEngineEnv.TANDEM_PROVIDER_STREAM_CONNECT_TIMEOUT_MS || "90000",
|
|
826
826
|
TANDEM_PROVIDER_STREAM_IDLE_TIMEOUT_MS:
|
|
827
827
|
existingEngineEnv.TANDEM_PROVIDER_STREAM_IDLE_TIMEOUT_MS || "90000",
|
|
828
828
|
TANDEM_PERMISSION_WAIT_TIMEOUT_MS:
|
|
@@ -1161,6 +1161,70 @@ async function writeManagedSearchSettings(payload = {}) {
|
|
|
1161
1161
|
};
|
|
1162
1162
|
}
|
|
1163
1163
|
|
|
1164
|
+
function getManagedSchedulerSettings() {
|
|
1165
|
+
const envPath = getManagedEngineEnvPath();
|
|
1166
|
+
const localEngine = isLocalEngineUrl(ENGINE_URL);
|
|
1167
|
+
const env = existsSync(envPath) ? parseDotEnv(readFileSync(envPath, "utf8")) : {};
|
|
1168
|
+
const modeRaw = String(env.TANDEM_SCHEDULER_MODE || "multi").trim().toLowerCase();
|
|
1169
|
+
const mode = modeRaw === "single" ? "single" : "multi";
|
|
1170
|
+
const maxRaw = Number.parseInt(String(env.TANDEM_SCHEDULER_MAX_CONCURRENT_RUNS || ""), 10);
|
|
1171
|
+
const maxConcurrentRuns = Number.isFinite(maxRaw) && maxRaw > 0 ? maxRaw : null;
|
|
1172
|
+
return {
|
|
1173
|
+
available: localEngine,
|
|
1174
|
+
local_engine: localEngine,
|
|
1175
|
+
writable: localEngine,
|
|
1176
|
+
managed_env_path: envPath,
|
|
1177
|
+
restart_required: false,
|
|
1178
|
+
restart_hint: "Restart tandem-engine after changing scheduler mode.",
|
|
1179
|
+
settings: {
|
|
1180
|
+
mode,
|
|
1181
|
+
max_concurrent_runs: maxConcurrentRuns,
|
|
1182
|
+
},
|
|
1183
|
+
reason: localEngine
|
|
1184
|
+
? ""
|
|
1185
|
+
: "Scheduler settings can only be edited here when the control panel points at a local engine host.",
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
async function writeManagedSchedulerSettings(payload = {}) {
|
|
1190
|
+
const current = getManagedSchedulerSettings();
|
|
1191
|
+
if (!current.local_engine) {
|
|
1192
|
+
const error = new Error(
|
|
1193
|
+
current.reason || "Scheduler settings are not editable for this engine."
|
|
1194
|
+
);
|
|
1195
|
+
error.statusCode = 400;
|
|
1196
|
+
throw error;
|
|
1197
|
+
}
|
|
1198
|
+
const envPath = current.managed_env_path;
|
|
1199
|
+
const existingEnv = existsSync(envPath) ? parseDotEnv(readFileSync(envPath, "utf8")) : {};
|
|
1200
|
+
const nextEnv = { ...existingEnv };
|
|
1201
|
+
const modeRaw = String(payload.mode || "multi").trim().toLowerCase();
|
|
1202
|
+
nextEnv.TANDEM_SCHEDULER_MODE = modeRaw === "single" ? "single" : "multi";
|
|
1203
|
+
if (payload.max_concurrent_runs != null && payload.max_concurrent_runs > 0) {
|
|
1204
|
+
nextEnv.TANDEM_SCHEDULER_MAX_CONCURRENT_RUNS = String(payload.max_concurrent_runs);
|
|
1205
|
+
} else {
|
|
1206
|
+
delete nextEnv.TANDEM_SCHEDULER_MAX_CONCURRENT_RUNS;
|
|
1207
|
+
}
|
|
1208
|
+
const preferredKeys = [
|
|
1209
|
+
"TANDEM_API_TOKEN",
|
|
1210
|
+
"TANDEM_STATE_DIR",
|
|
1211
|
+
"TANDEM_SCHEDULER_MODE",
|
|
1212
|
+
"TANDEM_SCHEDULER_MAX_CONCURRENT_RUNS",
|
|
1213
|
+
];
|
|
1214
|
+
const ordered = [];
|
|
1215
|
+
for (const key of preferredKeys) {
|
|
1216
|
+
if (nextEnv[key] !== undefined) ordered.push([key, nextEnv[key]]);
|
|
1217
|
+
}
|
|
1218
|
+
for (const [key, value] of Object.entries(nextEnv)) {
|
|
1219
|
+
if (!preferredKeys.includes(key)) ordered.push([key, value]);
|
|
1220
|
+
}
|
|
1221
|
+
await writeFile(envPath, serializeEnv(ordered), "utf8");
|
|
1222
|
+
return {
|
|
1223
|
+
...getManagedSchedulerSettings(),
|
|
1224
|
+
restart_required: true,
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1164
1228
|
function sendJson(res, code, payload) {
|
|
1165
1229
|
if (res.headersSent || res.writableEnded || res.destroyed) return;
|
|
1166
1230
|
const body = JSON.stringify(payload);
|
|
@@ -1395,7 +1459,7 @@ async function ensureEngineRunning() {
|
|
|
1395
1459
|
TANDEM_PROMPT_CONTEXT_HOOK_TIMEOUT_MS:
|
|
1396
1460
|
process.env.TANDEM_PROMPT_CONTEXT_HOOK_TIMEOUT_MS || "5000",
|
|
1397
1461
|
TANDEM_PROVIDER_STREAM_CONNECT_TIMEOUT_MS:
|
|
1398
|
-
process.env.TANDEM_PROVIDER_STREAM_CONNECT_TIMEOUT_MS || "
|
|
1462
|
+
process.env.TANDEM_PROVIDER_STREAM_CONNECT_TIMEOUT_MS || "90000",
|
|
1399
1463
|
TANDEM_PROVIDER_STREAM_IDLE_TIMEOUT_MS:
|
|
1400
1464
|
process.env.TANDEM_PROVIDER_STREAM_IDLE_TIMEOUT_MS || "90000",
|
|
1401
1465
|
TANDEM_PERMISSION_WAIT_TIMEOUT_MS: process.env.TANDEM_PERMISSION_WAIT_TIMEOUT_MS || "15000",
|
|
@@ -4796,6 +4860,29 @@ async function handleApi(req, res) {
|
|
|
4796
4860
|
return true;
|
|
4797
4861
|
}
|
|
4798
4862
|
|
|
4863
|
+
if (pathname === "/api/system/scheduler-settings" && req.method === "GET") {
|
|
4864
|
+
const session = requireSession(req, res);
|
|
4865
|
+
if (!session) return true;
|
|
4866
|
+
sendJson(res, 200, getManagedSchedulerSettings());
|
|
4867
|
+
return true;
|
|
4868
|
+
}
|
|
4869
|
+
|
|
4870
|
+
if (pathname === "/api/system/scheduler-settings" && req.method === "PATCH") {
|
|
4871
|
+
const session = requireSession(req, res);
|
|
4872
|
+
if (!session) return true;
|
|
4873
|
+
try {
|
|
4874
|
+
const payload = await readJsonBody(req);
|
|
4875
|
+
const saved = await writeManagedSchedulerSettings(payload);
|
|
4876
|
+
sendJson(res, 200, saved);
|
|
4877
|
+
} catch (error) {
|
|
4878
|
+
sendJson(res, Number(error?.statusCode || 500), {
|
|
4879
|
+
ok: false,
|
|
4880
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4881
|
+
});
|
|
4882
|
+
}
|
|
4883
|
+
return true;
|
|
4884
|
+
}
|
|
4885
|
+
|
|
4799
4886
|
if (pathname === "/api/auth/login" && req.method === "POST") {
|
|
4800
4887
|
await handleAuthLogin(req, res);
|
|
4801
4888
|
return true;
|