@frumu/tandem-panel 0.4.37 → 0.4.39

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/bin/setup.js CHANGED
@@ -22,7 +22,10 @@ import { resolveControlPanelPrincipalIdentity } from "../lib/setup/control-panel
22
22
  import { resolveControlPanelPreferencesPath } from "../lib/setup/control-panel-preferences.js";
23
23
  import { createSwarmApiHandler, getOrchestratorMetrics } from "../server/routes/swarm.js";
24
24
  import { createAcaApiHandler } from "../server/routes/aca.js";
25
- import { createCapabilitiesHandler, getCapabilitiesMetrics } from "../server/routes/capabilities.js";
25
+ import {
26
+ createCapabilitiesHandler,
27
+ getCapabilitiesMetrics,
28
+ } from "../server/routes/capabilities.js";
26
29
  import { createControlPanelConfigHandler } from "../server/routes/control-panel-config.js";
27
30
  import { createKnowledgebaseApiHandler } from "../server/routes/knowledgebase.js";
28
31
  import { createControlPanelPreferencesHandler } from "../server/routes/control-panel-preferences.js";
@@ -175,9 +178,7 @@ if (initRequested) {
175
178
  console.log("[Tandem Control Panel] Environment initialized.");
176
179
  console.log(`[Tandem Control Panel] .env: ${result.envPath}`);
177
180
  console.log(`[Tandem Control Panel] Engine URL: ${result.engineUrl}`);
178
- console.log(
179
- `[Tandem Control Panel] Panel URL: http://${result.panelHost}:${result.panelPort}`
180
- );
181
+ console.log(`[Tandem Control Panel] Panel URL: http://${result.panelHost}:${result.panelPort}`);
181
182
  console.log(`[Tandem Control Panel] Token: ${result.token}`);
182
183
  if (
183
184
  process.argv.slice(2).length === 1 ||
@@ -804,7 +805,9 @@ async function installServices() {
804
805
  const installEngine = serviceMode === "both" || serviceMode === "engine";
805
806
  const installPanel = serviceMode === "both" || serviceMode === "panel";
806
807
  const defaultStateDir = resolve(posixHomeForUser(serviceUser), ".local", "share", "tandem");
807
- const stateDir = String(process.env.TANDEM_HOME || process.env.TANDEM_STATE_DIR || defaultStateDir).trim();
808
+ const stateDir = String(
809
+ process.env.TANDEM_HOME || process.env.TANDEM_STATE_DIR || defaultStateDir
810
+ ).trim();
808
811
  const engineEnvPath = "/etc/tandem/engine.env";
809
812
  const panelEnvPath = "/etc/tandem/control-panel.env";
810
813
  const engineServiceName = "tandem-engine";
@@ -857,9 +860,7 @@ async function installServices() {
857
860
  ...(existingEngineEnv.TANDEM_EXA_API_KEY
858
861
  ? { TANDEM_EXA_API_KEY: existingEngineEnv.TANDEM_EXA_API_KEY }
859
862
  : {}),
860
- ...(existingEngineEnv.EXA_API_KEY
861
- ? { EXA_API_KEY: existingEngineEnv.EXA_API_KEY }
862
- : {}),
863
+ ...(existingEngineEnv.EXA_API_KEY ? { EXA_API_KEY: existingEngineEnv.EXA_API_KEY } : {}),
863
864
  ...(existingEngineEnv.TANDEM_SEARXNG_URL
864
865
  ? { TANDEM_SEARXNG_URL: existingEngineEnv.TANDEM_SEARXNG_URL }
865
866
  : {}),
@@ -1089,7 +1090,11 @@ async function readJsonBody(req) {
1089
1090
  }
1090
1091
 
1091
1092
  function normalizeSearchBackend(raw) {
1092
- switch (String(raw || "").trim().toLowerCase()) {
1093
+ switch (
1094
+ String(raw || "")
1095
+ .trim()
1096
+ .toLowerCase()
1097
+ ) {
1093
1098
  case "":
1094
1099
  case "auto":
1095
1100
  return "auto";
@@ -1107,7 +1112,9 @@ function normalizeSearchBackend(raw) {
1107
1112
  }
1108
1113
 
1109
1114
  function normalizeSearchUrl(raw) {
1110
- const value = String(raw || "").trim().replace(/\/+$/, "");
1115
+ const value = String(raw || "")
1116
+ .trim()
1117
+ .replace(/\/+$/, "");
1111
1118
  return value || "";
1112
1119
  }
1113
1120
 
@@ -1163,7 +1170,10 @@ async function writeManagedSearchSettings(payload = {}) {
1163
1170
  const nextEnv = { ...existingEnv };
1164
1171
 
1165
1172
  nextEnv.TANDEM_SEARCH_BACKEND = normalizeSearchBackend(payload.backend || "auto");
1166
- const timeoutRaw = Number.parseInt(String(payload.timeout_ms || payload.timeoutMs || "10000"), 10);
1173
+ const timeoutRaw = Number.parseInt(
1174
+ String(payload.timeout_ms || payload.timeoutMs || "10000"),
1175
+ 10
1176
+ );
1167
1177
  nextEnv.TANDEM_SEARCH_TIMEOUT_MS = String(
1168
1178
  Number.isFinite(timeoutRaw) ? Math.min(Math.max(timeoutRaw, 1000), 120000) : 10000
1169
1179
  );
@@ -1187,8 +1197,7 @@ async function writeManagedSearchSettings(payload = {}) {
1187
1197
  if (exaKey) {
1188
1198
  nextEnv.TANDEM_EXA_API_KEY = exaKey;
1189
1199
  delete nextEnv.TANDEM_EXA_SEARCH_API_KEY;
1190
- }
1191
- else if (payload.clear_exa_key || payload.clearExaKey) {
1200
+ } else if (payload.clear_exa_key || payload.clearExaKey) {
1192
1201
  delete nextEnv.TANDEM_EXA_API_KEY;
1193
1202
  delete nextEnv.TANDEM_EXA_SEARCH_API_KEY;
1194
1203
  delete nextEnv.EXA_API_KEY;
@@ -1224,7 +1233,9 @@ function getManagedSchedulerSettings() {
1224
1233
  const hostedManaged = isHostedManagedControlPanel();
1225
1234
  const available = localEngine || hostedManaged;
1226
1235
  const env = existsSync(envPath) ? parseDotEnv(readFileSync(envPath, "utf8")) : {};
1227
- const modeRaw = String(env.TANDEM_SCHEDULER_MODE || "multi").trim().toLowerCase();
1236
+ const modeRaw = String(env.TANDEM_SCHEDULER_MODE || "multi")
1237
+ .trim()
1238
+ .toLowerCase();
1228
1239
  const mode = modeRaw === "single" ? "single" : "multi";
1229
1240
  const maxRaw = Number.parseInt(String(env.TANDEM_SCHEDULER_MAX_CONCURRENT_RUNS || ""), 10);
1230
1241
  const maxConcurrentRuns = Number.isFinite(maxRaw) && maxRaw > 0 ? maxRaw : null;
@@ -1258,7 +1269,9 @@ async function writeManagedSchedulerSettings(payload = {}) {
1258
1269
  const envPath = current.managed_env_path;
1259
1270
  const existingEnv = existsSync(envPath) ? parseDotEnv(readFileSync(envPath, "utf8")) : {};
1260
1271
  const nextEnv = { ...existingEnv };
1261
- const modeRaw = String(payload.mode || "multi").trim().toLowerCase();
1272
+ const modeRaw = String(payload.mode || "multi")
1273
+ .trim()
1274
+ .toLowerCase();
1262
1275
  nextEnv.TANDEM_SCHEDULER_MODE = modeRaw === "single" ? "single" : "multi";
1263
1276
  if (payload.max_concurrent_runs != null && payload.max_concurrent_runs > 0) {
1264
1277
  nextEnv.TANDEM_SCHEDULER_MAX_CONCURRENT_RUNS = String(payload.max_concurrent_runs);
@@ -1307,9 +1320,7 @@ function readOptionalTokenFile(pathname) {
1307
1320
 
1308
1321
  function getAcaToken() {
1309
1322
  return (
1310
- String(process.env.ACA_API_TOKEN || "").trim() ||
1311
- readOptionalTokenFile(ACA_TOKEN_FILE) ||
1312
- ""
1323
+ String(process.env.ACA_API_TOKEN || "").trim() || readOptionalTokenFile(ACA_TOKEN_FILE) || ""
1313
1324
  );
1314
1325
  }
1315
1326
 
@@ -1780,7 +1791,9 @@ async function handleFilesApi(req, res, _session) {
1780
1791
  }
1781
1792
  }
1782
1793
  directories.sort((a, b) => String(a.name).localeCompare(String(b.name)));
1783
- files.sort((a, b) => b.updatedAt - a.updatedAt || String(a.name).localeCompare(String(b.name)));
1794
+ files.sort(
1795
+ (a, b) => b.updatedAt - a.updatedAt || String(a.name).localeCompare(String(b.name))
1796
+ );
1784
1797
  sendJson(res, 200, {
1785
1798
  ok: true,
1786
1799
  root: FILES_ROOT,
@@ -2069,14 +2082,39 @@ async function proxyEngineRequest(req, res, session) {
2069
2082
  const targetPath = incoming.pathname.replace(/^\/api\/engine/, "") || "/";
2070
2083
  const targetUrl = `${ENGINE_URL}${targetPath}${incoming.search}`;
2071
2084
  const forwardedHost = String(req.headers.host || "").trim();
2072
- const forwardedProto = String(req.headers["x-forwarded-proto"] || "").trim()
2073
- || (req.socket && req.socket.encrypted ? "https" : "http");
2085
+ const forwardedProto =
2086
+ String(req.headers["x-forwarded-proto"] || "").trim() ||
2087
+ (req.socket && req.socket.encrypted ? "https" : "http");
2088
+ const requestedSource = String(req.headers["x-tandem-request-source"] || "").trim();
2089
+ const requestedAgentId = String(req.headers["x-tandem-agent-id"] || "").trim();
2090
+ const agentTestMode = (() => {
2091
+ const raw = String(
2092
+ req.headers["x-tandem-agent-test-mode"] || req.headers["x-tandem-control-panel-agent-mode"] || ""
2093
+ ).trim()
2094
+ .toLowerCase();
2095
+ return ["1", "true", "yes", "on"].includes(raw);
2096
+ })();
2097
+ const requestSource = agentTestMode
2098
+ ? requestedSource || "agent"
2099
+ : "control_panel";
2074
2100
 
2075
2101
  const headers = new Headers();
2076
2102
  for (const [key, value] of Object.entries(req.headers)) {
2077
2103
  if (!value) continue;
2078
2104
  const lower = key.toLowerCase();
2079
- if (["host", "content-length", "cookie", "authorization", "x-tandem-token"].includes(lower)) {
2105
+ if (
2106
+ [
2107
+ "host",
2108
+ "content-length",
2109
+ "cookie",
2110
+ "authorization",
2111
+ "x-tandem-token",
2112
+ "x-tandem-agent-id",
2113
+ "x-tandem-agent-ancestor-ids",
2114
+ "x-tandem-control-panel-agent-mode",
2115
+ "x-tandem-agent-test-mode",
2116
+ ].includes(lower)
2117
+ ) {
2080
2118
  continue;
2081
2119
  }
2082
2120
  if (Array.isArray(value)) headers.set(key, value.join(", "));
@@ -2084,6 +2122,10 @@ async function proxyEngineRequest(req, res, session) {
2084
2122
  }
2085
2123
  headers.set("authorization", `Bearer ${session.token}`);
2086
2124
  headers.set("x-tandem-token", session.token);
2125
+ headers.set("x-tandem-request-source", requestSource);
2126
+ if (agentTestMode && requestedAgentId) {
2127
+ headers.set("x-tandem-agent-id", requestedAgentId);
2128
+ }
2087
2129
  if (forwardedHost) headers.set("x-forwarded-host", forwardedHost);
2088
2130
  if (forwardedProto) headers.set("x-forwarded-proto", forwardedProto);
2089
2131
 
@@ -2241,10 +2283,7 @@ async function engineRequestJson(session, path, options = {}) {
2241
2283
  }
2242
2284
  if (!response.ok) {
2243
2285
  const rawText = await response.text().catch(() => "");
2244
- if (
2245
- (response.status === 503 || isEngineStarting(rawText)) &&
2246
- attempt < maxNetworkRetries
2247
- ) {
2286
+ if ((response.status === 503 || isEngineStarting(rawText)) && attempt < maxNetworkRetries) {
2248
2287
  lastError = new Error(rawText || `${method} ${path} failed: ${response.status}`);
2249
2288
  response = null;
2250
2289
  continue;
@@ -2455,16 +2494,26 @@ function normalizePlannerTasks(rawTasks, maxTasks = 8, options = {}) {
2455
2494
  const linearFallback = options?.linearFallback === true;
2456
2495
  const candidates = Array.isArray(rawTasks) ? rawTasks : [];
2457
2496
  const normalizeTaskKind = (value, outputTarget) => {
2458
- const raw = String(value || "").trim().toLowerCase();
2497
+ const raw = String(value || "")
2498
+ .trim()
2499
+ .toLowerCase();
2459
2500
  if (["implementation", "inspection", "research", "validation"].includes(raw)) return raw;
2460
2501
  return outputTarget?.path ? "implementation" : "inspection";
2461
2502
  };
2462
2503
  const normalizeOutputTarget = (value) => {
2463
2504
  if (!value || typeof value !== "object") return null;
2464
- const path = String(value?.path || value?.file || value?.file_path || value?.target || "").trim();
2505
+ const path = String(
2506
+ value?.path || value?.file || value?.file_path || value?.target || ""
2507
+ ).trim();
2465
2508
  if (!path) return null;
2466
- const kind = String(value?.kind || value?.type || "artifact").trim().toLowerCase() || "artifact";
2467
- const operation = String(value?.operation || value?.mode || "").trim().toLowerCase() || "create_or_update";
2509
+ const kind =
2510
+ String(value?.kind || value?.type || "artifact")
2511
+ .trim()
2512
+ .toLowerCase() || "artifact";
2513
+ const operation =
2514
+ String(value?.operation || value?.mode || "")
2515
+ .trim()
2516
+ .toLowerCase() || "create_or_update";
2468
2517
  return { path, kind, operation };
2469
2518
  };
2470
2519
  const provisional = candidates
@@ -2567,8 +2616,12 @@ function inferOutputTargetFromText(text) {
2567
2616
  function ensurePlannerTaskOutputTargets(tasks, objective) {
2568
2617
  const list = Array.isArray(tasks) ? tasks : [];
2569
2618
  return list.map((task) => {
2570
- const taskKind = String(task?.taskKind || "").trim().toLowerCase() || "inspection";
2571
- const existing = task?.outputTarget && typeof task.outputTarget === "object" ? task.outputTarget : null;
2619
+ const taskKind =
2620
+ String(task?.taskKind || "")
2621
+ .trim()
2622
+ .toLowerCase() || "inspection";
2623
+ const existing =
2624
+ task?.outputTarget && typeof task.outputTarget === "object" ? task.outputTarget : null;
2572
2625
  return {
2573
2626
  ...task,
2574
2627
  taskKind,
@@ -2580,12 +2633,21 @@ function ensurePlannerTaskOutputTargets(tasks, objective) {
2580
2633
  function validateStrictPlannerTasks(tasks) {
2581
2634
  const list = Array.isArray(tasks) ? tasks : [];
2582
2635
  const invalidTaskKinds = list
2583
- .filter((task) => !["implementation", "inspection", "research", "validation"].includes(String(task?.taskKind || "").trim().toLowerCase()))
2636
+ .filter(
2637
+ (task) =>
2638
+ !["implementation", "inspection", "research", "validation"].includes(
2639
+ String(task?.taskKind || "")
2640
+ .trim()
2641
+ .toLowerCase()
2642
+ )
2643
+ )
2584
2644
  .map((task) => String(task?.id || task?.title || "task").trim())
2585
2645
  .filter(Boolean);
2586
2646
  const missing = list
2587
2647
  .filter((task) => {
2588
- const taskKind = String(task?.taskKind || "").trim().toLowerCase();
2648
+ const taskKind = String(task?.taskKind || "")
2649
+ .trim()
2650
+ .toLowerCase();
2589
2651
  return taskKind === "implementation" && !String(task?.outputTarget?.path || "").trim();
2590
2652
  })
2591
2653
  .map((task) => String(task?.id || task?.title || "task").trim())
@@ -2888,7 +2950,9 @@ function workerWriteRetryToolAllowlist() {
2888
2950
  }
2889
2951
 
2890
2952
  function strictWriteRetryEnabled() {
2891
- const raw = String(process.env.TANDEM_STRICT_WRITE_RETRY_ENABLED || "").trim().toLowerCase();
2953
+ const raw = String(process.env.TANDEM_STRICT_WRITE_RETRY_ENABLED || "")
2954
+ .trim()
2955
+ .toLowerCase();
2892
2956
  if (!raw) return true;
2893
2957
  return !["0", "false", "no", "off"].includes(raw);
2894
2958
  }
@@ -2910,7 +2974,9 @@ function nonWritingRetryMaxAttempts() {
2910
2974
  }
2911
2975
 
2912
2976
  function classifyStrictWriteFailureReason(verification) {
2913
- const reason = String(verification?.reason || "").trim().toUpperCase();
2977
+ const reason = String(verification?.reason || "")
2978
+ .trim()
2979
+ .toUpperCase();
2914
2980
  if (!reason || reason === "VERIFIED") return "";
2915
2981
  if (
2916
2982
  reason === "WRITE_ARGS_EMPTY_FROM_PROVIDER" ||
@@ -2958,7 +3024,9 @@ function buildStrictWriteRetryRequest(prompt, verification, attemptIndex, maxAtt
2958
3024
  recoveryLines.push("- Then create or modify the required file in the same turn.");
2959
3025
  } else {
2960
3026
  recoveryLines.push("- Do not inspect further with read/search/glob/ls/list.");
2961
- recoveryLines.push("- Create or modify the required target directly with write/edit/apply_patch.");
3027
+ recoveryLines.push(
3028
+ "- Create or modify the required target directly with write/edit/apply_patch."
3029
+ );
2962
3030
  }
2963
3031
  return {
2964
3032
  parts: [{ type: "text", text: recoveryLines.join("\n") }],
@@ -2971,7 +3039,9 @@ function buildStrictWriteRetryRequest(prompt, verification, attemptIndex, maxAtt
2971
3039
  }
2972
3040
 
2973
3041
  function classifyNonWritingFailureReason(verification) {
2974
- const reason = String(verification?.reason || "").trim().toUpperCase();
3042
+ const reason = String(verification?.reason || "")
3043
+ .trim()
3044
+ .toUpperCase();
2975
3045
  if (!reason || reason === "VERIFIED") return "";
2976
3046
  if (reason === "NO_TOOL_ACTIVITY_NO_DECISION" || reason === "NO_TOOL_ACTIVITY") {
2977
3047
  return "no_tool_activity";
@@ -3011,9 +3081,7 @@ function buildNonWritingRetryRequest(prompt, verification, attemptIndex, maxAtte
3011
3081
 
3012
3082
  const WRITE_TOOL_NAMES = new Set(["write", "edit", "apply_patch"]);
3013
3083
  const REQUIRED_TOOL_MODE_REASON = "TOOL_MODE_REQUIRED_NOT_SATISFIED";
3014
- const REQUIRED_TOOL_REASON_PATTERN = new RegExp(
3015
- `${REQUIRED_TOOL_MODE_REASON}:\\s*([A-Z_]+)\\b`
3016
- );
3084
+ const REQUIRED_TOOL_REASON_PATTERN = new RegExp(`${REQUIRED_TOOL_MODE_REASON}:\\s*([A-Z_]+)\\b`);
3017
3085
 
3018
3086
  function normalizeVerificationMode(mode) {
3019
3087
  return String(mode || "")
@@ -3057,7 +3125,9 @@ function extractRequiredToolFailureReason(text) {
3057
3125
  const raw = String(text || "").trim();
3058
3126
  if (!raw) return "";
3059
3127
  const match = raw.match(REQUIRED_TOOL_REASON_PATTERN);
3060
- return String(match?.[1] || "").trim().toUpperCase();
3128
+ return String(match?.[1] || "")
3129
+ .trim()
3130
+ .toUpperCase();
3061
3131
  }
3062
3132
 
3063
3133
  function shouldTrackWorkspacePath(pathname) {
@@ -3484,7 +3554,9 @@ function summarizeExecutionRows(rows, limit = 12) {
3484
3554
  for (const row of list) {
3485
3555
  if (out.length >= limit) break;
3486
3556
  const role = roleOfMessage(row);
3487
- const type = String(row?.type || "").trim().toLowerCase();
3557
+ const type = String(row?.type || "")
3558
+ .trim()
3559
+ .toLowerCase();
3488
3560
  const text = textOfMessage(row).trim();
3489
3561
  const parts = Array.isArray(row?.parts) ? row.parts : [];
3490
3562
  const tools = [];
@@ -3505,7 +3577,15 @@ function summarizeExecutionRows(rows, limit = 12) {
3505
3577
  return out;
3506
3578
  }
3507
3579
 
3508
- function buildAttemptTelemetry(name, request, rows, messages, startedAtMs, error = null, meta = {}) {
3580
+ function buildAttemptTelemetry(
3581
+ name,
3582
+ request,
3583
+ rows,
3584
+ messages,
3585
+ startedAtMs,
3586
+ error = null,
3587
+ meta = {}
3588
+ ) {
3509
3589
  const syncAudit = collectToolActivity(rows, `${name}_prompt_sync`);
3510
3590
  const sessionAudit = collectToolActivity(messages, `${name}_session_snapshot`);
3511
3591
  const merged = mergeToolActivityAudits(syncAudit, sessionAudit);
@@ -3561,8 +3641,7 @@ function buildVerificationSummary(syncRows, messages, workspaceChanges, sessionI
3561
3641
  else if (requiredToolModeUnsatisfied) reason = REQUIRED_TOOL_MODE_REASON;
3562
3642
  else if (toolAudit.rejectedWriteToolCalls > 0)
3563
3643
  reason = "WRITE_TOOL_ATTEMPT_REJECTED_NO_WORKSPACE_CHANGE";
3564
- else if (toolAudit.rejectedToolCalls > 0)
3565
- reason = "TOOL_ATTEMPT_REJECTED_NO_WORKSPACE_CHANGE";
3644
+ else if (toolAudit.rejectedToolCalls > 0) reason = "TOOL_ATTEMPT_REJECTED_NO_WORKSPACE_CHANGE";
3566
3645
  else if (strictMode && toolAudit.totalToolCalls > 0)
3567
3646
  reason = "NO_WRITE_ACTIVITY_NO_WORKSPACE_CHANGE";
3568
3647
  else reason = "NO_TOOL_ACTIVITY_NO_WORKSPACE_CHANGE";
@@ -3668,10 +3747,9 @@ async function resolveExecutionModel(session, run) {
3668
3747
  function normalizeSessionModelRef(value) {
3669
3748
  if (typeof value === "string") return value.trim();
3670
3749
  if (value && typeof value === "object") {
3671
- const model =
3672
- String(
3673
- value?.model_id || value?.id || value?.name || value?.slug || value?.model || ""
3674
- ).trim();
3750
+ const model = String(
3751
+ value?.model_id || value?.id || value?.name || value?.slug || value?.model || ""
3752
+ ).trim();
3675
3753
  if (model) return model;
3676
3754
  }
3677
3755
  return "";
@@ -3684,7 +3762,9 @@ function summarizeRunStepsForPrompt(run, currentStepId, limit = 12) {
3684
3762
  .map((row, index) => {
3685
3763
  const stepId = String(row?.step_id || `step-${index + 1}`).trim();
3686
3764
  const title = String(row?.title || stepId).trim();
3687
- const status = String(row?.status || "unknown").trim().toLowerCase();
3765
+ const status = String(row?.status || "unknown")
3766
+ .trim()
3767
+ .toLowerCase();
3688
3768
  const marker = stepId === currentStepId ? "*" : "-";
3689
3769
  return `${marker} ${stepId} [${status}]: ${title}`;
3690
3770
  })
@@ -3695,8 +3775,7 @@ function summarizeRunStepsForPrompt(run, currentStepId, limit = 12) {
3695
3775
  function stepPromptText(run, step, stepIndex, totalSteps) {
3696
3776
  const stepId = String(step?.step_id || "").trim() || `step-${stepIndex + 1}`;
3697
3777
  const stepTitle = String(step?.title || stepId).trim();
3698
- const stepDetails =
3699
- step && typeof step === "object" ? JSON.stringify(step, null, 2).trim() : "";
3778
+ const stepDetails = step && typeof step === "object" ? JSON.stringify(step, null, 2).trim() : "";
3700
3779
  const stepList = summarizeRunStepsForPrompt(run, stepId);
3701
3780
  return [
3702
3781
  "Execute this swarm step.",
@@ -3796,7 +3875,13 @@ function buildNonWritingVerificationSummary(syncRows, messages, sessionId, optio
3796
3875
  };
3797
3876
  }
3798
3877
 
3799
- async function runExecutionPromptWithVerification(session, run, prompt, sessionId = "", options = {}) {
3878
+ async function runExecutionPromptWithVerification(
3879
+ session,
3880
+ run,
3881
+ prompt,
3882
+ sessionId = "",
3883
+ options = {}
3884
+ ) {
3800
3885
  const activeSessionId =
3801
3886
  String(sessionId || "").trim() || (await createExecutionSession(session, run));
3802
3887
  if (!activeSessionId) throw new Error("Failed to create execution session.");
@@ -3886,7 +3971,9 @@ async function runExecutionPromptWithVerification(session, run, prompt, sessionI
3886
3971
  `/session/${encodeURIComponent(activeSessionId)}`
3887
3972
  ).catch(() => null);
3888
3973
  lastSessionSnapshot = sessionSnapshot;
3889
- const sessionMessages = Array.isArray(sessionSnapshot?.messages) ? sessionSnapshot.messages : [];
3974
+ const sessionMessages = Array.isArray(sessionSnapshot?.messages)
3975
+ ? sessionSnapshot.messages
3976
+ : [];
3890
3977
  messages = rowsSinceAttemptStart(sessionMessages, previousMessageCount);
3891
3978
  previousMessageCount = sessionMessages.length;
3892
3979
  hasAssistant = syncRows.some((row) => roleOfMessage(row) === "assistant");
@@ -3951,8 +4038,7 @@ async function runExecutionPromptWithVerification(session, run, prompt, sessionI
3951
4038
  normalizeSessionModelRef(lastSessionSnapshot?.model) ||
3952
4039
  normalizeSessionModelRef(resolvedModel?.model),
3953
4040
  source: String(
3954
- lastSessionSnapshot?.provider &&
3955
- normalizeSessionModelRef(lastSessionSnapshot?.model)
4041
+ lastSessionSnapshot?.provider && normalizeSessionModelRef(lastSessionSnapshot?.model)
3956
4042
  ? "session_snapshot"
3957
4043
  : resolvedModel?.source || ""
3958
4044
  ).trim(),
@@ -4048,7 +4134,9 @@ function taskTitleFromRecord(task) {
4048
4134
 
4049
4135
  function taskKindFromRecord(task) {
4050
4136
  const payload = task?.payload && typeof task.payload === "object" ? task.payload : {};
4051
- const raw = String(payload?.task_kind || task?.task_type || "inspection").trim().toLowerCase();
4137
+ const raw = String(payload?.task_kind || task?.task_type || "inspection")
4138
+ .trim()
4139
+ .toLowerCase();
4052
4140
  return ["implementation", "inspection", "research", "validation"].includes(raw)
4053
4141
  ? raw
4054
4142
  : "inspection";
@@ -4065,7 +4153,9 @@ function summarizeBlackboardTasksForPrompt(tasks, currentTaskId, limit = 16) {
4065
4153
  .map((task, index) => {
4066
4154
  const taskId = String(task?.id || `task-${index + 1}`).trim();
4067
4155
  const title = taskTitleFromRecord(task);
4068
- const status = String(task?.status || "unknown").trim().toLowerCase();
4156
+ const status = String(task?.status || "unknown")
4157
+ .trim()
4158
+ .toLowerCase();
4069
4159
  const marker = taskId === currentTaskId ? "*" : "-";
4070
4160
  return `${marker} ${taskId} [${status}]: ${title}`;
4071
4161
  })
@@ -4077,8 +4167,7 @@ function taskPromptText(run, task, workerId, workflowId) {
4077
4167
  const taskId = String(task?.id || "").trim();
4078
4168
  const taskTitle = taskTitleFromRecord(task);
4079
4169
  const taskKind = taskKindFromRecord(task);
4080
- const taskDetails =
4081
- task && typeof task === "object" ? JSON.stringify(task, null, 2).trim() : "";
4170
+ const taskDetails = task && typeof task === "object" ? JSON.stringify(task, null, 2).trim() : "";
4082
4171
  const taskList = summarizeBlackboardTasksForPrompt(run?.tasks, taskId);
4083
4172
  const outputTarget =
4084
4173
  task?.payload?.output_target && typeof task.payload.output_target === "object"
@@ -4220,10 +4309,14 @@ async function seedBlackboardTasks(session, runId, objective, taskRows, workflow
4220
4309
  if (!prepared.length) {
4221
4310
  throw new Error("No valid tasks to seed.");
4222
4311
  }
4223
- const created = await engineRequestJson(session, `/context/runs/${encodeURIComponent(runId)}/tasks`, {
4224
- method: "POST",
4225
- body: { tasks: prepared },
4226
- });
4312
+ const created = await engineRequestJson(
4313
+ session,
4314
+ `/context/runs/${encodeURIComponent(runId)}/tasks`,
4315
+ {
4316
+ method: "POST",
4317
+ body: { tasks: prepared },
4318
+ }
4319
+ );
4227
4320
  if (created && created.ok === false) {
4228
4321
  throw new Error(String(created.error || created.code || "Task seeding failed."));
4229
4322
  }
@@ -4525,13 +4618,19 @@ async function driveBlackboardRunExecution(session, runId, options = {}) {
4525
4618
  if (swarmExecutors.has(runId)) return false;
4526
4619
  const controller = getSwarmRunController(runId);
4527
4620
  const workflowId = String(
4528
- options.workflowId || controller?.workflowId || swarmState.workflowId || "swarm.blackboard.default"
4621
+ options.workflowId ||
4622
+ controller?.workflowId ||
4623
+ swarmState.workflowId ||
4624
+ "swarm.blackboard.default"
4529
4625
  ).trim();
4530
4626
  const maxAgents = Math.max(
4531
4627
  1,
4532
4628
  Math.min(
4533
4629
  16,
4534
- Number.parseInt(String(options.maxAgents || controller?.maxAgents || swarmState.maxAgents || 3), 10) || 3
4630
+ Number.parseInt(
4631
+ String(options.maxAgents || controller?.maxAgents || swarmState.maxAgents || 3),
4632
+ 10
4633
+ ) || 3
4535
4634
  )
4536
4635
  );
4537
4636
  upsertSwarmRunController(runId, {
@@ -4856,7 +4955,10 @@ async function startSwarm(session, config = {}) {
4856
4955
  await synced;
4857
4956
  let plannerTasks = [];
4858
4957
  let planSeedMode = "fallback_local";
4859
- const enforceStrictTaskOutputs = String(verificationMode || "strict").trim().toLowerCase() === "strict";
4958
+ const enforceStrictTaskOutputs =
4959
+ String(verificationMode || "strict")
4960
+ .trim()
4961
+ .toLowerCase() === "strict";
4860
4962
  try {
4861
4963
  const llmPlan = await generatePlanTodosWithLLM(session, run, maxTasks);
4862
4964
  let plannerSource = "llm_objective_planner";
@@ -4912,12 +5014,18 @@ async function startSwarm(session, config = {}) {
4912
5014
  if (enforceStrictTaskOutputs) {
4913
5015
  const strictCheck = validateStrictPlannerTasks(plannerTasks);
4914
5016
  if (!strictCheck.ok) {
4915
- await appendContextRunEvent(session, runId, "plan_failed_output_target_missing", "planning", {
4916
- reason: plannerFailureReason,
4917
- missing_tasks: strictCheck.missing,
4918
- invalid_task_kind_tasks: strictCheck.invalidTaskKinds,
4919
- recovery_source: recovered.source,
4920
- }).catch(() => null);
5017
+ await appendContextRunEvent(
5018
+ session,
5019
+ runId,
5020
+ "plan_failed_output_target_missing",
5021
+ "planning",
5022
+ {
5023
+ reason: plannerFailureReason,
5024
+ missing_tasks: strictCheck.missing,
5025
+ invalid_task_kind_tasks: strictCheck.invalidTaskKinds,
5026
+ recovery_source: recovered.source,
5027
+ }
5028
+ ).catch(() => null);
4921
5029
  throw new Error(
4922
5030
  `Strict orchestration requires valid task kinds and output targets where needed: missing_output_target=${strictCheck.missing.join(", ")} invalid_task_kind=${strictCheck.invalidTaskKinds.join(", ")}`
4923
5031
  );
@@ -5251,7 +5359,10 @@ async function handleApi(req, res) {
5251
5359
  return handleControlPanelPreferences(req, res, session);
5252
5360
  }
5253
5361
 
5254
- if (pathname === "/api/control-panel/config" && (req.method === "GET" || req.method === "PATCH")) {
5362
+ if (
5363
+ pathname === "/api/control-panel/config" &&
5364
+ (req.method === "GET" || req.method === "PATCH")
5365
+ ) {
5255
5366
  const session = requireSession(req, res);
5256
5367
  if (!session) return true;
5257
5368
  return handleControlPanelConfig(req, res);