Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions src/support/dashboardHtml.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Auto-generated: Dashboard HTML template for OpenSwarm Supervisor
const DASHBOARD_HTML = `<!DOCTYPE html>
<html lang="en">
Expand Down Expand Up @@ -318,7 +318,9 @@
case "process:spawn":
fetchProcesses();
fetchCoordination();
addLogLine({ taskId: ev.data.taskId || "system", stage: ev.data.stage || "spawn", line: "Process spawned PID=" + ev.data.pid + " stage=" + ev.data.stage + (ev.data.model ? " model=" + ev.data.model : "") });
const spawnLine = "Process spawned PID=" + ev.data.pid + " stage=" + ev.data.stage +
(ev.data.model ? " model=" + ev.data.model : "");
addLogLine({ taskId: ev.data.taskId || "system", stage: ev.data.stage || "spawn", line: spawnLine });
break;
case "process:exit":
fetchProcesses();
Expand Down Expand Up @@ -420,7 +422,8 @@
const priorityColor = issue.priority === 1 ? 'var(--red)' : issue.priority === 2 ? 'var(--amber)' : 'var(--dim)';
html += '<div style="margin-bottom: 6px; padding: 4px; border-left: 2px solid ' + priorityColor + '; background: rgba(255, 170, 0, 0.05);">';
const title = String(issue.title || '');
html += '<div style="color: var(--white); font-size:12px; margin-bottom: 2px;">' + escapeHtml(issue.identifier) + ': ' + escapeHtml(title.substring(0, 40)) + (title.length > 40 ? '...' : '') + '</div>';
const issueTitle = escapeHtml(issue.identifier) + ': ' + escapeHtml(title.substring(0, 40)) + (title.length > 40 ? '...' : '');
html += '<div style="color: var(--white); font-size:12px; margin-bottom: 2px;">' + issueTitle + '</div>';
html += '<div style="color: var(--amber); font-size:11px;">' + escapeHtml(issue.reason) + '</div>';
if (issue.project?.name) {
html += '<div style="color: var(--dim); font-size:11px; margin-top: 2px;">📁 ' + escapeHtml(issue.project.name) + '</div>';
Expand All @@ -437,7 +440,8 @@
const priorityColor = issue.priority === 1 ? 'var(--red)' : issue.priority === 2 ? 'var(--amber)' : 'var(--dim)';
html += '<div style="margin-bottom: 6px; padding: 4px; border-left: 2px solid ' + priorityColor + '; background: rgba(255, 51, 51, 0.05);">';
const title = String(issue.title || '');
html += '<div style="color: var(--white); font-size:12px; margin-bottom: 2px;">' + escapeHtml(issue.identifier) + ': ' + escapeHtml(title.substring(0, 40)) + (title.length > 40 ? '...' : '') + '</div>';
const issueTitle = escapeHtml(issue.identifier) + ': ' + escapeHtml(title.substring(0, 40)) + (title.length > 40 ? '...' : '');
html += '<div style="color: var(--white); font-size:12px; margin-bottom: 2px;">' + issueTitle + '</div>';
html += '<div style="color: var(--red); font-size:11px;">' + escapeHtml(issue.reason) + '</div>';
if (issue.project?.name) {
html += '<div style="color: var(--dim); font-size:11px; margin-top: 2px;">📁 ' + escapeHtml(issue.project.name) + '</div>';
Expand Down Expand Up @@ -483,7 +487,8 @@
html += '<div><span style="color: var(--dim);">Repos:</span> <span style="color: var(--text);">' + (data.repos?.length || 0) + '</span></div>';

if (data.currentPR) {
html += '<div><span style="color: var(--amber);">Processing:</span> <span style="color: var(--text); font-family: var(--font-mono); font-size:12px;">' + escapeHtml(data.currentPR) + '</span></div>';
const prValue = '<span style="color: var(--text); font-family: var(--font-mono); font-size:12px;">' + escapeHtml(data.currentPR) + '</span>';
html += '<div><span style="color: var(--amber);">Processing:</span> ' + prValue + '</div>';
}

html += '<div><span style="color: var(--dim);">Last run:</span> <span style="color: var(--text);">' + formatTime(data.lastRun) + '</span></div>';
Expand Down Expand Up @@ -802,7 +807,7 @@
"modules:" + s.totalModules + " tests:" + s.totalTestFiles +
" untested:" + s.untestedModules.length +
" churn:" + (s.avgChurnScore || 0).toFixed(2) +
(s.hotModules.length ? " hot:" + s.hotModules.slice(0,3).map(function(m){return m.split("/").pop()}).join(",") : "") +
(s.hotModules.length ? " hot:" + s.hotModules.slice(0,3).map(function(m){return escapeHtml(m.split("/").pop())}).join(",") : "") +
"</div>"
);
}
Expand Down Expand Up @@ -943,10 +948,12 @@
var parts = name.split("-");
return parts[parts.length - 1];
}
const TOKEN_MILLION = 1000000;
const TOKEN_THOUSAND = 1000;
function fmtTokens(n) {
if (n == null) return "";
if (n >= 1000000) return (n / 1000000).toFixed(1) + "M";
if (n >= 1000) return (n / 1000).toFixed(1) + "k";
if (n >= TOKEN_MILLION) return (n / 1000000).toFixed(1) + "M";
if (n >= TOKEN_THOUSAND) return (n / 1000).toFixed(1) + "k";
return String(n);
}
function buildStageDetails(r) {
Expand All @@ -969,7 +976,7 @@

if (r.summary) addLine("Summary", escapeHtml(r.summary));
if (r.decision) {
const cls = "sd-decision-" + r.decision;
const cls = "sd-decision-" + safeCssClass(r.decision);
addLine("Decision", "<span class=\\"" + cls + "\\">" + escapeHtml(r.decision.toUpperCase()) + "</span>");
}
if (r.feedback) addLine("Feedback", escapeHtml(r.feedback));
Expand Down Expand Up @@ -1046,7 +1053,7 @@
// without having to expand.
let inlineSummary = "";
if (r.decision) {
const cls = "sd-decision-" + r.decision;
const cls = "sd-decision-" + safeCssClass(r.decision);
inlineSummary = "<span class=\\"" + cls + "\\">" + escapeHtml(r.decision.toUpperCase()) + "</span>" +
(r.feedback ? " · " + escapeHtml(r.feedback.slice(0, 80)) : "");
} else if (r.summary) {
Expand Down Expand Up @@ -1304,6 +1311,12 @@
function escapeAttr(text) {
return String(text || "").replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function safeCssClass(text) {
const cleaned = String(text || "").replace(/[^a-zA-Z0-9_-]/g, "_");
// Never emit an empty class token: "sd-decision-" + "" would produce a
// dangling selector prefix that can match unintended elements.
return cleaned.length > 0 ? cleaned : "_";
}
function escapeJsArgAttr(text) {
return escapeAttr(JSON.stringify(String(text || "")));
}
Expand Down Expand Up @@ -1624,7 +1637,7 @@
'<span class="proc-pid">' + lead + '</span>' +
'<span class="proc-stage">' + escapeHtml(p.stage) + '</span>' +
'<span class="proc-model">' + escapeHtml(modelStr) + '</span>' +
'<span style="color:var(--dim);font-size:11px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escapeAttr(p.projectPath || "") + '">' + escapeHtml(projName) + '</span>' +
'<span class="proc-proj" title="' + escapeAttr(p.projectPath || "") + '">' + escapeHtml(projName) + '</span>' +
'<span class="proc-activity">' + act + '</span>' +
'<span class="proc-dur">' + dur + '</span>' +
btn +
Expand Down
11 changes: 9 additions & 2 deletions src/support/gitTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,19 +313,26 @@ function runGitCommand(cwd: string, args: string[], env?: NodeJS.ProcessEnv): Pr
let stdout = '';
let stderr = '';
let settled = false;
// Streaming UTF-8 decoders: state is kept across chunks so a multibyte
// character split at a chunk boundary is not corrupted (unlike data.toString()).
const stdoutDecoder = new TextDecoder('utf-8');
const stderrDecoder = new TextDecoder('utf-8');
const timer = setTimeout(() => {
if (settled) return;
settled = true; // claim settlement before the late close/error handlers run
proc.kill('SIGKILL');
reject(new Error(`git ${args.join(' ')} timed out after ${GIT_CMD_TIMEOUT_MS}ms`));
}, GIT_CMD_TIMEOUT_MS);

proc.stdout.on('data', (data) => { stdout += data.toString(); });
proc.stderr.on('data', (data) => { stderr += data.toString(); });
proc.stdout.on('data', (data) => { stdout += stdoutDecoder.decode(data, { stream: true }); });
proc.stderr.on('data', (data) => { stderr += stderrDecoder.decode(data, { stream: true }); });

proc.on('close', (code) => {
settled = true;
clearTimeout(timer);
// Flush any remaining bytes held by the decoders (truncated final multibyte char)
stdout += stdoutDecoder.decode();
stderr += stderrDecoder.decode();
if (code === 0) {
resolve(stdout);
} else {
Expand Down
Loading