@yemi33/minions 0.1.2047 → 0.1.2049
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/dashboard/js/command-center.js +72 -0
- package/dashboard/js/command-history.js +2 -1
- package/dashboard/js/command-input.js +7 -3
- package/dashboard/js/command-parser.js +1 -0
- package/dashboard/js/detail-panel.js +7 -0
- package/dashboard/js/fre.js +1 -0
- package/dashboard/js/live-stream.js +2 -0
- package/dashboard/js/modal-qa.js +9 -0
- package/dashboard/js/modal.js +1 -0
- package/dashboard/js/qa.js +6 -0
- package/dashboard/js/refresh.js +3 -1
- package/dashboard/js/render-agents.js +30 -3
- package/dashboard/js/render-dispatch.js +12 -0
- package/dashboard/js/render-inbox.js +6 -0
- package/dashboard/js/render-kb.js +4 -0
- package/dashboard/js/render-managed.js +1 -0
- package/dashboard/js/render-meetings.js +6 -0
- package/dashboard/js/render-other.js +11 -0
- package/dashboard/js/render-pinned.js +2 -0
- package/dashboard/js/render-pipelines.js +5 -0
- package/dashboard/js/render-plans.js +9 -0
- package/dashboard/js/render-prd.js +8 -0
- package/dashboard/js/render-prs.js +4 -0
- package/dashboard/js/render-schedules.js +5 -0
- package/dashboard/js/render-skills.js +2 -0
- package/dashboard/js/render-watches.js +10 -0
- package/dashboard/js/render-work-items.js +9 -0
- package/dashboard/js/settings.js +10 -0
- package/dashboard/slim.html +8 -8
- package/docs/deprecated.json +7 -3
- package/engine/cleanup.js +24 -1
- package/engine/lifecycle.js +0 -1
- package/engine/queries.js +37 -5
- package/package.json +6 -3
- package/playbooks/fix.md +15 -5
- package/playbooks/shared-rules.md +29 -0
|
@@ -310,6 +310,7 @@ function renderPipelines(pipelines) {
|
|
|
310
310
|
const start = _pipelinePage * PIPELINE_PER_PAGE;
|
|
311
311
|
const pageItems = pipelines.slice(start, start + PIPELINE_PER_PAGE);
|
|
312
312
|
|
|
313
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: pipeline id, title, trigger, resources, stages)
|
|
313
314
|
el.innerHTML = pageItems.map(function(p) {
|
|
314
315
|
const activeRun = _getPipelineActiveRun(p);
|
|
315
316
|
const lastRun = (p.runs || []).slice(-1)[0];
|
|
@@ -351,6 +352,7 @@ function renderPipelines(pipelines) {
|
|
|
351
352
|
}).join('');
|
|
352
353
|
|
|
353
354
|
if (pipelines.length > PIPELINE_PER_PAGE) {
|
|
355
|
+
// eslint-disable-next-line no-unsanitized/method -- reason: composed from internal numeric page bounds and fixed pager actions (no user data flows in)
|
|
354
356
|
el.insertAdjacentHTML('beforeend', '<div class="pr-pager">' +
|
|
355
357
|
'<span class="pr-page-info">Showing ' + (start + 1) + ' to ' + Math.min(start + PIPELINE_PER_PAGE, pipelines.length) + ' of ' + pipelines.length + '</span>' +
|
|
356
358
|
'<div class="pr-pager-btns">' +
|
|
@@ -474,6 +476,7 @@ function openPipelineDetail(id) {
|
|
|
474
476
|
html += '</div>';
|
|
475
477
|
|
|
476
478
|
document.getElementById('modal-title').textContent = 'Pipeline: ' + displayTitle;
|
|
479
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes user-controlled stage output before assembling HTML; all other user data wrapped in escHtml() (see dashboard/js/utils.js)
|
|
477
480
|
document.getElementById('modal-body').innerHTML = html;
|
|
478
481
|
document.getElementById('modal').classList.add('open');
|
|
479
482
|
|
|
@@ -684,6 +687,7 @@ function openCreatePipelineModal() {
|
|
|
684
687
|
}).join('');
|
|
685
688
|
|
|
686
689
|
document.getElementById('modal-title').textContent = 'New Pipeline';
|
|
690
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: composed from fixed scheduler option arrays and escaped timezone (no user data flows in)
|
|
687
691
|
document.getElementById('modal-body').innerHTML =
|
|
688
692
|
'<div style="display:flex;flex-direction:column;gap:10px">' +
|
|
689
693
|
'<label style="color:var(--text);font-size:var(--text-md)">ID<input id="pl-id" style="' + inputStyle + '" placeholder="e.g. daily-audit-cycle"></label>' +
|
|
@@ -785,6 +789,7 @@ function openEditPipelineModal(id) {
|
|
|
785
789
|
window._editPipelineId = id;
|
|
786
790
|
|
|
787
791
|
document.getElementById('modal-title').textContent = 'Edit Pipeline: ' + displayTitle;
|
|
792
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: pipeline id, title, stages JSON, timezone)
|
|
788
793
|
document.getElementById('modal-body').innerHTML =
|
|
789
794
|
'<div style="display:flex;flex-direction:column;gap:10px">' +
|
|
790
795
|
'<div style="color:var(--muted);font-size:11px">ID: <strong style="color:var(--text)">' + escHtml(id) + '</strong></div>' +
|
|
@@ -13,6 +13,7 @@ function openCreatePlanModal() {
|
|
|
13
13
|
const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit';
|
|
14
14
|
|
|
15
15
|
document.getElementById('modal-title').textContent = 'Create Plan';
|
|
16
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: project names)
|
|
16
17
|
document.getElementById('modal-body').innerHTML =
|
|
17
18
|
'<div style="display:flex;flex-direction:column;gap:10px">' +
|
|
18
19
|
'<label style="color:var(--text);font-size:var(--text-md)">Title <input id="plan-new-title" style="' + inputStyle + '" placeholder="e.g. Add user authentication with JWT"></label>' +
|
|
@@ -383,6 +384,7 @@ function renderPlans(plans) {
|
|
|
383
384
|
'</div>';
|
|
384
385
|
}
|
|
385
386
|
|
|
387
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: plan file, summary, project, generatedBy, revision feedback, action target files)
|
|
386
388
|
el.innerHTML = html;
|
|
387
389
|
restoreNotifBadges();
|
|
388
390
|
}
|
|
@@ -413,6 +415,7 @@ function openArchivedPlansModal() {
|
|
|
413
415
|
}).join('');
|
|
414
416
|
|
|
415
417
|
document.getElementById('modal-title').textContent = 'Archived Plans';
|
|
418
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: archived plan file, summary, project, generatedBy)
|
|
416
419
|
document.getElementById('modal-body').innerHTML = html;
|
|
417
420
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
418
421
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
@@ -456,6 +459,7 @@ async function planExecute(file, project, btn) {
|
|
|
456
459
|
function planReexecuteModal(file, project) {
|
|
457
460
|
const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit';
|
|
458
461
|
document.getElementById('modal-title').textContent = 'Re-execute Plan';
|
|
462
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: plan file, project)
|
|
459
463
|
document.getElementById('modal-body').innerHTML =
|
|
460
464
|
'<div style="display:flex;flex-direction:column;gap:10px">' +
|
|
461
465
|
'<div style="font-size:12px;color:var(--muted)">' + escapeHtml(file) + '</div>' +
|
|
@@ -545,6 +549,7 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
|
|
|
545
549
|
if (normalizedFile.endsWith('.json')) {
|
|
546
550
|
let plan;
|
|
547
551
|
try { plan = JSON.parse(raw); } catch (e) {
|
|
552
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: parse error message, raw plan excerpt)
|
|
548
553
|
document.getElementById('modal-body').innerHTML = '<p style="color:var(--red)">Failed to parse plan JSON: ' + escapeHtml(e.message) + '</p><pre style="font-size:10px;max-height:200px;overflow:auto">' + escapeHtml((raw || '').slice(0, 500)) + '</pre>';
|
|
549
554
|
return;
|
|
550
555
|
}
|
|
@@ -632,6 +637,7 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
|
|
|
632
637
|
const lastModLabel = lastMod ? '<div style="font-size:10px;color:var(--muted);font-weight:400;margin-top:2px">Last updated: ' + formatLocalDateTime(lastMod) + '</div>' : '';
|
|
633
638
|
const actionBtns = '<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px">' + modalActions + '</div>';
|
|
634
639
|
|
|
640
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: plan title, version label, action target files)
|
|
635
641
|
document.getElementById('modal-title').innerHTML = escapeHtml(title) + (versionLabel ? ' <span style="font-size:11px;font-weight:700;padding:1px 6px;border-radius:3px;background:rgba(56,139,253,0.15);color:var(--blue)">' + escapeHtml(versionLabel) + '</span>' : '') + lastModLabel + actionBtns;
|
|
636
642
|
const modalBody = document.getElementById('modal-body');
|
|
637
643
|
const scrollTop = modalBody.scrollTop;
|
|
@@ -640,6 +646,7 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
|
|
|
640
646
|
modalBody.style.fontFamily = 'Consolas, monospace';
|
|
641
647
|
modalBody.style.whiteSpace = 'pre-wrap';
|
|
642
648
|
} else {
|
|
649
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
|
|
643
650
|
modalBody.innerHTML = renderMd(text);
|
|
644
651
|
}
|
|
645
652
|
modalBody.scrollTop = scrollTop;
|
|
@@ -882,6 +889,7 @@ async function planOpenInDocChat(file) {
|
|
|
882
889
|
document.getElementById('modal-body').style.fontFamily = 'Consolas, monospace';
|
|
883
890
|
document.getElementById('modal-body').style.whiteSpace = 'pre-wrap';
|
|
884
891
|
} else {
|
|
892
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
|
|
885
893
|
document.getElementById('modal-body').innerHTML = renderMd(text);
|
|
886
894
|
}
|
|
887
895
|
_modalDocContext = { title: title, content: text, selection: '' };
|
|
@@ -932,6 +940,7 @@ async function openVerifyGuide(file) {
|
|
|
932
940
|
const content = await fetch('/api/plans/' + encodeURIComponent(normalizedFile)).then(r => r.text());
|
|
933
941
|
document.getElementById('modal-title').innerHTML = 'Manual Testing Guide' +
|
|
934
942
|
' <button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;margin-left:8px;vertical-align:middle" onclick="openArchivedPrdModal()">Back</button>';
|
|
943
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
|
|
935
944
|
document.getElementById('modal-body').innerHTML = renderMd(content);
|
|
936
945
|
_modalDocContext = { title: 'Manual Testing Guide', content, selection: '' };
|
|
937
946
|
_modalFilePath = 'prd/' + normalizedFile; showModalQa();
|
|
@@ -44,6 +44,7 @@ async function _savePrdDesc(source, itemId) {
|
|
|
44
44
|
});
|
|
45
45
|
if (res.ok) {
|
|
46
46
|
_prdDescRawCache = newDesc;
|
|
47
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
|
|
47
48
|
view.innerHTML = newDesc ? renderMd(newDesc) : '<span style="color:var(--muted);font-size:11px">No description</span>';
|
|
48
49
|
view.style.display = '';
|
|
49
50
|
editor.style.display = 'none';
|
|
@@ -116,6 +117,7 @@ function renderPrd(prd, prog) {
|
|
|
116
117
|
' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;margin-left:4px" onclick="planArchive(\'' + escHtml(prdFile) + '\',this)">Archive</button>';
|
|
117
118
|
}
|
|
118
119
|
}
|
|
120
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: PRD file; status label/color and age text are derived UI state)
|
|
119
121
|
badge.innerHTML = '<span style="font-weight:600;font-size:11px;color:' + (statusColors[effectiveStatus] || 'var(--muted)') + '">' + (statusLabels[effectiveStatus] || effectiveStatus) + '</span>' +
|
|
120
122
|
' <span style="color:var(--muted);font-size:10px">' + (prd.age || '') + '</span>' + actions;
|
|
121
123
|
} else {
|
|
@@ -134,6 +136,7 @@ function renderPrd(prd, prog) {
|
|
|
134
136
|
const parts = Object.entries(counts).filter(([, n]) => n > 0).map(([s, n]) =>
|
|
135
137
|
'<span style="font-size:10px;color:' + (statusColors[s] || 'var(--muted)') + '">' + n + ' ' + (statusLabels[s] || s).toLowerCase() + '</span>'
|
|
136
138
|
);
|
|
139
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: composed from compile-time constants (no user data flows in)
|
|
137
140
|
badge.innerHTML = '<span style="font-weight:600;font-size:11px;color:var(--text)">' + existing.length + ' PRDs</span> ' + parts.join(' · ');
|
|
138
141
|
}
|
|
139
142
|
section.innerHTML = '';
|
|
@@ -644,6 +647,7 @@ function renderPrdProgress(prog) {
|
|
|
644
647
|
'</div>';
|
|
645
648
|
}
|
|
646
649
|
|
|
650
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() or renderMd() by PRD renderers (fields: item ids/names/descriptions, source, projects, PR links, branches, agent labels)
|
|
647
651
|
el.innerHTML = html;
|
|
648
652
|
restoreNotifBadges();
|
|
649
653
|
}
|
|
@@ -686,6 +690,7 @@ function openArchivedPrdModal() {
|
|
|
686
690
|
}
|
|
687
691
|
|
|
688
692
|
document.getElementById('modal-title').textContent = 'Archived PRDs';
|
|
693
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: archived PRD file, summary, projects, completion date)
|
|
689
694
|
document.getElementById('modal-body').innerHTML = html;
|
|
690
695
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
691
696
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
@@ -725,6 +730,7 @@ function showArchivedPrdDetail(idxOrFile) {
|
|
|
725
730
|
'</div>';
|
|
726
731
|
|
|
727
732
|
document.getElementById('modal-title').textContent = g.summary || g.file;
|
|
733
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() or renderMd() by archived PRD renderers (fields: PRD file, group header, items, stats, E2E links)
|
|
728
734
|
document.getElementById('modal-body').innerHTML = toggleHtml + content;
|
|
729
735
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
730
736
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
@@ -816,6 +822,7 @@ async function prdItemEdit(source, itemId) {
|
|
|
816
822
|
'</div>';
|
|
817
823
|
|
|
818
824
|
document.getElementById('modal-title').textContent = item.id + ' — ' + (item.name || '').slice(0, 60);
|
|
825
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() or renderMd() (fields: PRD item name/description, source, item id, agent, completion summary, PR links)
|
|
819
826
|
document.getElementById('modal-body').innerHTML = html;
|
|
820
827
|
document.getElementById('modal-body').style.fontFamily = '';
|
|
821
828
|
document.getElementById('modal-body').style.whiteSpace = '';
|
|
@@ -1003,6 +1010,7 @@ function openArchive(i) {
|
|
|
1003
1010
|
|
|
1004
1011
|
html += '</div>';
|
|
1005
1012
|
|
|
1013
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: archive summary, feature ids/names/descriptions/statuses, rationale, affected areas, open questions)
|
|
1006
1014
|
document.getElementById('modal-body').innerHTML = html;
|
|
1007
1015
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
1008
1016
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
@@ -170,6 +170,7 @@ function renderPrs(prs) {
|
|
|
170
170
|
|
|
171
171
|
const tableWrap = el.querySelector('.pr-table-wrap');
|
|
172
172
|
const savedScroll = tableWrap ? tableWrap.scrollLeft : 0;
|
|
173
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all PR user data wrapped in escapeHtml() by prRow() (fields: PR id, title, description, agent, branch, review/build/status labels)
|
|
173
174
|
el.innerHTML = prTableHtml(rows) + pager;
|
|
174
175
|
if (savedScroll) {
|
|
175
176
|
const newWrap = el.querySelector('.pr-table-wrap');
|
|
@@ -184,6 +185,7 @@ function openAllPrs() {
|
|
|
184
185
|
const modalEl = document.querySelector('#modal .modal');
|
|
185
186
|
if (modalEl) modalEl.classList.add('modal-wide');
|
|
186
187
|
document.getElementById('modal-title').textContent = 'All Pull Requests (' + allPrs.length + ')';
|
|
188
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all PR user data wrapped in escapeHtml() by prRow() (fields: PR id, title, description, agent, branch, review/build/status labels)
|
|
187
189
|
document.getElementById('modal-body').innerHTML = prTableHtml(allPrs.map(prRow).join(''));
|
|
188
190
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
189
191
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
@@ -194,6 +196,7 @@ function openModal(i) {
|
|
|
194
196
|
const item = inboxData[i];
|
|
195
197
|
if (!item) return;
|
|
196
198
|
document.getElementById('modal-title').textContent = item.name;
|
|
199
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes item.content before assembling HTML; item.name is wrapped in escapeHtml() (see dashboard/js/utils.js)
|
|
197
200
|
document.getElementById('modal-body').innerHTML =
|
|
198
201
|
'<div style="margin-bottom:12px"><button class="pr-pager-btn" style="font-size:10px;padding:3px 10px" onclick="promoteToKB(\'' + escapeHtml(item.name) + '\')">Add to Knowledge Base</button></div>' +
|
|
199
202
|
'<div style="font-size:12px;line-height:1.7;color:var(--muted)">' + renderMd(item.content) + '</div>';
|
|
@@ -224,6 +227,7 @@ function openAddPrModal() {
|
|
|
224
227
|
: '';
|
|
225
228
|
|
|
226
229
|
document.getElementById('modal-title').textContent = 'Link Pull Request';
|
|
230
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: project names)
|
|
227
231
|
document.getElementById('modal-body').innerHTML =
|
|
228
232
|
'<div style="display:flex;flex-direction:column;gap:10px">' +
|
|
229
233
|
noProjectsWarning +
|
|
@@ -369,6 +369,7 @@ function renderSchedules(schedules) {
|
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by schedule renderers (fields: schedule id/title/cron/type/project/agent/description)
|
|
372
373
|
el.innerHTML = html;
|
|
373
374
|
}
|
|
374
375
|
|
|
@@ -382,6 +383,7 @@ function openScheduleDetail(id) {
|
|
|
382
383
|
const lastRun = s._lastRun ? formatLocalDateTime(s._lastRun) : 'never';
|
|
383
384
|
const enabledLabel = s.enabled ? '<span class="pr-badge approved">enabled</span>' : '<span class="pr-badge rejected">disabled</span>';
|
|
384
385
|
|
|
386
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: schedule title, schedule id)
|
|
385
387
|
document.getElementById('modal-title').innerHTML = escHtml(s.title || s.id) +
|
|
386
388
|
' <div style="display:flex;gap:4px;margin-top:4px">' +
|
|
387
389
|
'<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" onclick="runScheduleNow(\'' + escHtml(s.id) + '\',this)">Run now</button>' +
|
|
@@ -402,6 +404,7 @@ function openScheduleDetail(id) {
|
|
|
402
404
|
(s.description ? '<div><strong style="color:var(--muted)">Description:</strong><div style="margin-top:4px;padding:8px;background:var(--surface2);border-radius:4px">' + renderMd(s.description) + '</div></div>' : '') +
|
|
403
405
|
'</div>';
|
|
404
406
|
|
|
407
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() or renderMd() (fields: schedule id/title/cron/type/priority/project/agent/description/last run)
|
|
405
408
|
document.getElementById('modal-body').innerHTML = body;
|
|
406
409
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
407
410
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
@@ -518,6 +521,7 @@ function openCreateScheduleModal() {
|
|
|
518
521
|
document.getElementById('modal-title').textContent = 'New Scheduled Task';
|
|
519
522
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
520
523
|
document.getElementById('modal-body').style.fontFamily = '';
|
|
524
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by _scheduleFormHtml() (fields: project names, agent ids/names)
|
|
521
525
|
document.getElementById('modal-body').innerHTML = _scheduleFormHtml({}, false);
|
|
522
526
|
document.getElementById('modal').classList.add('open');
|
|
523
527
|
_updateCronPreview();
|
|
@@ -530,6 +534,7 @@ function openEditScheduleModal(id) {
|
|
|
530
534
|
document.getElementById('modal-title').textContent = 'Edit Schedule: ' + id;
|
|
531
535
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
532
536
|
document.getElementById('modal-body').style.fontFamily = '';
|
|
537
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by _scheduleFormHtml() (fields: schedule id/title/cron/description, project names, agent ids/names)
|
|
533
538
|
document.getElementById('modal-body').innerHTML = _scheduleFormHtml(sched, true);
|
|
534
539
|
window._editScheduleId = id;
|
|
535
540
|
document.getElementById('modal').classList.add('open');
|
|
@@ -113,6 +113,7 @@ function renderSkills(skills) {
|
|
|
113
113
|
html += '</div>';
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() or _jsArg() (fields: skill file/source/dir/name/description/tab labels)
|
|
116
117
|
el.innerHTML = html;
|
|
117
118
|
window._lastSkills = skills;
|
|
118
119
|
}
|
|
@@ -131,6 +132,7 @@ function openSkill(file, source, dir) {
|
|
|
131
132
|
.then(r => r.text())
|
|
132
133
|
.then(content => {
|
|
133
134
|
document.getElementById('modal-title').textContent = file;
|
|
135
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
|
|
134
136
|
document.getElementById('modal-body').innerHTML = renderMd(content);
|
|
135
137
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
136
138
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
@@ -187,6 +187,7 @@ function renderWatches(watchesData) {
|
|
|
187
187
|
'</div>';
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: watch id/target/description/action type/owner/timestamps)
|
|
190
191
|
el.innerHTML = html;
|
|
191
192
|
}
|
|
192
193
|
|
|
@@ -278,6 +279,7 @@ function openWatchDetail(id) {
|
|
|
278
279
|
var targetLabel = _targetTypeLabel(w.targetType);
|
|
279
280
|
var condLabel = _conditionLabel(w.condition);
|
|
280
281
|
|
|
282
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: watch description/target/id)
|
|
281
283
|
document.getElementById('modal-title').innerHTML = escHtml(w.description || w.target) +
|
|
282
284
|
' <div style="display:flex;gap:4px;margin-top:4px">' +
|
|
283
285
|
(w.status === 'active' ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--yellow)" onclick="toggleWatchPause(\'' + escHtml(w.id) + '\',true);closeModal()">Pause</button>' : '') +
|
|
@@ -318,6 +320,7 @@ function openWatchDetail(id) {
|
|
|
318
320
|
'<div id="watch-history-' + escHtml(w.id) + '"><div style="color:var(--muted);font-style:italic;font-size:11px">Loading history…</div></div>' +
|
|
319
321
|
'</div>';
|
|
320
322
|
|
|
323
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by watch detail renderers (fields: watch id/target/owner/description/action params/requires/history/result messages)
|
|
321
324
|
document.getElementById('modal-body').innerHTML = body;
|
|
322
325
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
323
326
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
@@ -336,10 +339,12 @@ function openWatchDetail(id) {
|
|
|
336
339
|
return res.json();
|
|
337
340
|
}).then(function(data) {
|
|
338
341
|
if (!container.isConnected) return; // user closed the modal
|
|
342
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: _renderWatchHistoryDetail() escapes all user-controlled fields before assembling HTML
|
|
339
343
|
container.innerHTML = _renderWatchHistoryDetail(data && data.history);
|
|
340
344
|
}).catch(function() {
|
|
341
345
|
if (!container.isConnected) return;
|
|
342
346
|
// Fall back to the inline _history from /api/watches (best-effort).
|
|
347
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: _renderWatchHistoryDetail() escapes all user-controlled fields before assembling HTML
|
|
343
348
|
container.innerHTML = _renderWatchHistoryDetail(w._history || []);
|
|
344
349
|
});
|
|
345
350
|
})();
|
|
@@ -489,6 +494,7 @@ function _addWatchStepRow(initial) {
|
|
|
489
494
|
row.className = 'watch-step-row';
|
|
490
495
|
row.setAttribute('data-step-idx', String(idx));
|
|
491
496
|
row.style.cssText = 'border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px;display:flex;flex-direction:column;gap:6px;background:var(--surface)';
|
|
497
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: action registry values/labels/descriptions)
|
|
492
498
|
row.innerHTML =
|
|
493
499
|
'<div style="display:flex;align-items:center;justify-content:space-between;gap:8px">' +
|
|
494
500
|
'<select id="watch-step-' + idx + '-type" style="' + inputStyle + ';margin-top:0;flex:1" onchange="_updateWatchStepParamsHint(' + idx + ')">' + actionOpts + '</select>' +
|
|
@@ -551,6 +557,7 @@ function _addRequireRow(initial) {
|
|
|
551
557
|
row.className = 'watch-require-row';
|
|
552
558
|
row.setAttribute('data-require-idx', String(idx));
|
|
553
559
|
row.style.cssText = 'display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:6px;align-items:end';
|
|
560
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: target type registry values/labels)
|
|
554
561
|
row.innerHTML =
|
|
555
562
|
'<label style="color:var(--text);font-size:11px;margin:0">Target<input id="watch-require-' + idx + '-target" placeholder="e.g. PR-123, pipeline-X" style="' + inputStyle + '"></label>' +
|
|
556
563
|
'<label style="color:var(--text);font-size:11px;margin:0">Target Type<select id="watch-require-' + idx + '-target-type" style="' + inputStyle + '" onchange="_updateRequireConditionOptions(' + idx + ')">' + ttOpts + '</select></label>' +
|
|
@@ -576,6 +583,7 @@ function _updateRequireConditionOptions(idx) {
|
|
|
576
583
|
if (!ttSel || !condSel || !_watchTargetTypesCache) return;
|
|
577
584
|
var entry = _watchTargetTypesCache.find(function(t) { return t.value === ttSel.value; });
|
|
578
585
|
var conditions = (entry && entry.conditions) || [];
|
|
586
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: condition registry values, condition labels)
|
|
579
587
|
condSel.innerHTML = (conditions.length ? '' : '<option value="">— select target type first —</option>') +
|
|
580
588
|
conditions.map(function(c) {
|
|
581
589
|
return '<option value="' + escHtml(c) + '">' + escHtml(_conditionLabel(c)) + '</option>';
|
|
@@ -589,6 +597,7 @@ function _updateWatchConditionOptions() {
|
|
|
589
597
|
if (!ttSel || !condSel || !_watchTargetTypesCache) return;
|
|
590
598
|
var entry = _watchTargetTypesCache.find(function(t) { return t.value === ttSel.value; });
|
|
591
599
|
var conditions = (entry && entry.conditions) || [];
|
|
600
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: condition registry values, condition labels)
|
|
592
601
|
condSel.innerHTML = conditions.map(function(c) {
|
|
593
602
|
return '<option value="' + escHtml(c) + '">' + escHtml(_conditionLabel(c)) + '</option>';
|
|
594
603
|
}).join('');
|
|
@@ -597,6 +606,7 @@ function _updateWatchConditionOptions() {
|
|
|
597
606
|
function _renderCreateWatchModal() {
|
|
598
607
|
document.getElementById('modal-title').innerHTML = 'Create Watch' +
|
|
599
608
|
' <button class="pr-pager-btn" style="font-size:10px;padding:2px 12px;color:var(--green);border-color:var(--green);margin-left:8px" onclick="submitWatch()">Create</button>';
|
|
609
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by _watchFormHtml() (fields: target/action registries, project names, agent ids/names)
|
|
600
610
|
document.getElementById('modal-body').innerHTML = _watchFormHtml();
|
|
601
611
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
602
612
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
@@ -149,6 +149,7 @@ function renderWorkItems(items) {
|
|
|
149
149
|
|
|
150
150
|
const tableWrap = el.querySelector('.pr-table-wrap');
|
|
151
151
|
const savedScroll = tableWrap ? tableWrap.scrollLeft : 0;
|
|
152
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() by wiRow() (fields: work item id/title/source/status/agent/PR links/dates/follow-up metadata)
|
|
152
153
|
el.innerHTML = html;
|
|
153
154
|
if (savedScroll) {
|
|
154
155
|
const newWrap = el.querySelector('.pr-table-wrap');
|
|
@@ -200,6 +201,7 @@ async function editWorkItem(id, source) {
|
|
|
200
201
|
|
|
201
202
|
document.getElementById('modal-title').textContent = 'Edit Work Item ' + id;
|
|
202
203
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
204
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: title, description, references, acceptance criteria, dependencies, agent ids/names, work item id/source)
|
|
203
205
|
document.getElementById('modal-body').innerHTML =
|
|
204
206
|
'<div style="display:flex;flex-direction:column;gap:12px;font-family:inherit">' +
|
|
205
207
|
'<label style="color:var(--text);font-size:var(--text-md)">Title' +
|
|
@@ -320,6 +322,7 @@ async function toggleWorkItemArchive() {
|
|
|
320
322
|
try {
|
|
321
323
|
const items = await fetch('/api/work-items/archive').then(r => r.json());
|
|
322
324
|
if (!items.length) { el.innerHTML = '<p class="empty">No archived work items.</p>'; return; }
|
|
325
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: archived work item id/title/type/status/agent)
|
|
323
326
|
el.innerHTML = '<div style="font-size:10px;color:var(--muted);margin-bottom:6px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px">Archived (' + items.length + ')</div>' +
|
|
324
327
|
'<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th><th>Agent</th><th>Archived</th></tr></thead><tbody>' +
|
|
325
328
|
items.map(function(i) {
|
|
@@ -371,6 +374,7 @@ let _feedbackRating = null;
|
|
|
371
374
|
function feedbackWorkItem(id, source) {
|
|
372
375
|
_feedbackRating = null;
|
|
373
376
|
document.getElementById('modal-title').textContent = 'Feedback on ' + id;
|
|
377
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: work item id, source)
|
|
374
378
|
document.getElementById('modal-body').innerHTML =
|
|
375
379
|
'<div style="display:flex;flex-direction:column;gap:16px">' +
|
|
376
380
|
'<div style="display:flex;gap:16px;justify-content:center">' +
|
|
@@ -431,6 +435,7 @@ function openCreateWorkItemModal() {
|
|
|
431
435
|
const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit';
|
|
432
436
|
|
|
433
437
|
document.getElementById('modal-title').textContent = 'Create Work Item';
|
|
438
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: agent ids/names, project names)
|
|
434
439
|
document.getElementById('modal-body').innerHTML =
|
|
435
440
|
'<div style="display:flex;flex-direction:column;gap:10px">' +
|
|
436
441
|
'<label style="color:var(--text);font-size:var(--text-md)">Title <input id="wi-new-title" style="' + inputStyle + '" placeholder="What needs to be done?"></label>' +
|
|
@@ -646,6 +651,7 @@ function openWorkItemDetail(id) {
|
|
|
646
651
|
|
|
647
652
|
const initial = needsHydration ? Object.assign({}, cached, { _descriptionLoading: true }) : cached;
|
|
648
653
|
document.getElementById('modal-title').textContent = initial.title || initial.id;
|
|
654
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() or renderMd() by _wiRenderDetail() (fields: title, description, agent, source, reasons, references, artifacts, PR links)
|
|
649
655
|
document.getElementById('modal-body').innerHTML = _wiRenderDetail(initial);
|
|
650
656
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
651
657
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
@@ -669,6 +675,7 @@ function openWorkItemDetail(id) {
|
|
|
669
675
|
merged.description = full.description || cached.description || '';
|
|
670
676
|
if (Array.isArray(full.acceptanceCriteria)) merged.acceptanceCriteria = full.acceptanceCriteria;
|
|
671
677
|
if (Array.isArray(full.references)) merged.references = full.references;
|
|
678
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() or renderMd() by _wiRenderDetail() (fields: title, description, agent, source, reasons, references, artifacts, PR links)
|
|
672
679
|
document.getElementById('modal-body').innerHTML = _wiRenderDetail(merged);
|
|
673
680
|
})
|
|
674
681
|
.catch(function() {
|
|
@@ -681,6 +688,7 @@ function openAllWorkItems() {
|
|
|
681
688
|
document.getElementById('modal-title').textContent = 'All Work Items (' + allWorkItems.length + ')';
|
|
682
689
|
const html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Source</th><th>Type</th><th>Priority</th><th>Status</th><th>Agent</th><th>PR</th><th>Created</th><th></th><th></th></tr></thead><tbody>' +
|
|
683
690
|
allWorkItems.map(wiRow).join('') + '</tbody></table></div>';
|
|
691
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() by wiRow() (fields: work item id/title/source/status/agent/PR links/dates/follow-up metadata)
|
|
684
692
|
document.getElementById('modal-body').innerHTML = html;
|
|
685
693
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
686
694
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
@@ -696,6 +704,7 @@ function viewAgentOutput(logPath) {
|
|
|
696
704
|
fetch('/api/agent-output?file=' + encodeURIComponent(logPath))
|
|
697
705
|
.then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
|
|
698
706
|
.then(function(content) {
|
|
707
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: renderAgentOutput() escapes all user-controlled fields before assembling HTML (see dashboard/js/render-utils.js)
|
|
699
708
|
document.getElementById('modal-body').innerHTML = renderAgentOutput(content);
|
|
700
709
|
})
|
|
701
710
|
.catch(function() {
|
package/dashboard/js/settings.js
CHANGED
|
@@ -351,6 +351,7 @@ async function openSettings() {
|
|
|
351
351
|
saveBtn.onclick = saveSettings;
|
|
352
352
|
actions.insertBefore(saveBtn, actions.lastElementChild);
|
|
353
353
|
}
|
|
354
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: engine, claude, agents, projects, routing, features)
|
|
354
355
|
document.getElementById('modal-body').innerHTML = '<div id="settings-status" style="font-size:11px;min-height:16px;margin-bottom:4px"></div>' + html;
|
|
355
356
|
document.getElementById('modal-body').style.fontFamily = '';
|
|
356
357
|
document.getElementById('modal-body').style.whiteSpace = '';
|
|
@@ -411,9 +412,11 @@ async function initRuntimeFleetUI(engineCfg, agentsCfg) {
|
|
|
411
412
|
const names = runtimes.length ? runtimes.map(rt => rt.name) : ['copilot'];
|
|
412
413
|
const currentDefault = engineCfg.defaultCli || 'copilot';
|
|
413
414
|
const currentCc = engineCfg.ccCli || '';
|
|
415
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: runtime names)
|
|
414
416
|
cliSelect.innerHTML = names.map(n =>
|
|
415
417
|
'<option value="' + escHtml(n) + '"' + (n === currentDefault ? ' selected' : '') + '>' + escHtml(n) + '</option>'
|
|
416
418
|
).join('');
|
|
419
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: runtime names)
|
|
417
420
|
ccCliSelect.innerHTML =
|
|
418
421
|
'<option value=""' + (!currentCc ? ' selected' : '') + '>Inherit Default CLI</option>' +
|
|
419
422
|
names.map(n =>
|
|
@@ -427,6 +430,7 @@ async function initRuntimeFleetUI(engineCfg, agentsCfg) {
|
|
|
427
430
|
const agentId = cell.getAttribute('data-runtime-cli');
|
|
428
431
|
const agent = (agentsCfg || {})[agentId] || {};
|
|
429
432
|
const current = agent.cli || '';
|
|
433
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: agentId, runtime names)
|
|
430
434
|
cell.innerHTML =
|
|
431
435
|
'<select data-agent="' + escHtml(agentId) + '" data-field="cli" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:11px">' +
|
|
432
436
|
'<option value=""' + (!current ? ' selected' : '') + '>(fleet default)</option>' +
|
|
@@ -486,6 +490,7 @@ async function loadModelsForRuntime(runtimeName, inputId, currentValue) {
|
|
|
486
490
|
if (!wrap) return;
|
|
487
491
|
const token = _nextModelLoadToken('runtime', inputId);
|
|
488
492
|
if (!runtimeName) {
|
|
493
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: currentValue; inputId is an internal fixed DOM id)
|
|
489
494
|
wrap.innerHTML = '<input id="' + inputId + '" value="' + escHtml(currentValue || '') + '" placeholder="(no runtime selected)" disabled style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--muted);font-size:12px">';
|
|
490
495
|
return;
|
|
491
496
|
}
|
|
@@ -500,6 +505,7 @@ async function loadModelsForRuntime(runtimeName, inputId, currentValue) {
|
|
|
500
505
|
if (!models || models.length === 0) {
|
|
501
506
|
// Free-text fallback — let the user type anything (custom Anthropic /
|
|
502
507
|
// OpenAI model IDs, future models, etc.).
|
|
508
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: currentValue; inputId is an internal fixed DOM id)
|
|
503
509
|
wrap.innerHTML = '<input id="' + inputId + '" value="' + escHtml(currentValue || '') + '" placeholder="Default (CLI chooses)" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">';
|
|
504
510
|
return;
|
|
505
511
|
}
|
|
@@ -516,6 +522,7 @@ async function loadModelsForRuntime(runtimeName, inputId, currentValue) {
|
|
|
516
522
|
if (currentValue && !models.some(m => (m.id || m.name) === currentValue)) {
|
|
517
523
|
opts += '<option value="' + escHtml(currentValue) + '" selected>' + escHtml(currentValue) + ' (custom)</option>';
|
|
518
524
|
}
|
|
525
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: model id, model label, currentValue; inputId is an internal fixed DOM id)
|
|
519
526
|
wrap.innerHTML = '<select id="' + inputId + '" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">' + opts + '</select>';
|
|
520
527
|
}
|
|
521
528
|
|
|
@@ -533,6 +540,7 @@ async function loadModelsForAgent(agentId, runtimeName, currentValue) {
|
|
|
533
540
|
const baseStyle = 'width:120px;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:11px';
|
|
534
541
|
const token = _nextModelLoadToken('agent', agentId);
|
|
535
542
|
if (!runtimeName) {
|
|
543
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: agentId, currentValue)
|
|
536
544
|
cell.innerHTML = '<input ' + baseAttrs + ' value="' + escHtml(currentValue || '') + '" placeholder="(no runtime)" disabled style="' + baseStyle + ';color:var(--muted)">';
|
|
537
545
|
return;
|
|
538
546
|
}
|
|
@@ -545,6 +553,7 @@ async function loadModelsForAgent(agentId, runtimeName, currentValue) {
|
|
|
545
553
|
if (!_isCurrentModelLoad('agent', agentId, token)) return;
|
|
546
554
|
const models = Array.isArray(payload.models) ? payload.models : null;
|
|
547
555
|
if (!models || models.length === 0) {
|
|
556
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: agentId, runtimeName, currentValue)
|
|
548
557
|
cell.innerHTML = '<input ' + baseAttrs + ' value="' + escHtml(currentValue || '') + '" placeholder="' + escHtml(runtimeName) + ' default" style="' + baseStyle + '">';
|
|
549
558
|
return;
|
|
550
559
|
}
|
|
@@ -559,6 +568,7 @@ async function loadModelsForAgent(agentId, runtimeName, currentValue) {
|
|
|
559
568
|
if (currentValue && !models.some(m => (m.id || m.name) === currentValue)) {
|
|
560
569
|
opts += '<option value="' + escHtml(currentValue) + '" selected>' + escHtml(currentValue) + ' (custom — invalid for ' + escHtml(runtimeName) + '?)</option>';
|
|
561
570
|
}
|
|
571
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: agentId, model id, model label, currentValue, runtimeName)
|
|
562
572
|
cell.innerHTML = '<select ' + baseAttrs + ' style="' + baseStyle + '">' + opts + '</select>';
|
|
563
573
|
}
|
|
564
574
|
|
package/dashboard/slim.html
CHANGED
|
@@ -919,10 +919,10 @@
|
|
|
919
919
|
var flags = (data && data.features) || [];
|
|
920
920
|
|
|
921
921
|
// Build the body as a document fragment so we avoid .innerHTML for
|
|
922
|
-
// the dynamic parts
|
|
923
|
-
//
|
|
924
|
-
//
|
|
925
|
-
// (
|
|
922
|
+
// the dynamic parts. Static intro paragraph uses innerHTML because
|
|
923
|
+
// it carries a <code> tag and is a pure string literal (exempted
|
|
924
|
+
// by the no-unsanitized rule).
|
|
925
|
+
// (Comment retained for legibility; ratchet test is being retired.)
|
|
926
926
|
var intro = document.createElement('p');
|
|
927
927
|
intro.innerHTML = 'Toggle <code>slim-ux</code> off to return to the original dashboard. Advanced settings (agents, projects, runtime) live there for now.';
|
|
928
928
|
var frag = document.createDocumentFragment();
|
|
@@ -1342,8 +1342,8 @@
|
|
|
1342
1342
|
// Zero-project: a hint to add one. Selection persists per-browser in
|
|
1343
1343
|
// localStorage and is included in every /api/command-center/stream call.
|
|
1344
1344
|
// Build the "+ Add" button as a real DOM node (was an HTML string + innerHTML
|
|
1345
|
-
// injection —
|
|
1346
|
-
//
|
|
1345
|
+
// injection — the no-unsanitized/property rule prefers we avoid that pattern
|
|
1346
|
+
// entirely for dynamic data).
|
|
1347
1347
|
function makeContextAddBtn() {
|
|
1348
1348
|
var btn = document.createElement('button');
|
|
1349
1349
|
btn.type = 'button';
|
|
@@ -1911,8 +1911,8 @@
|
|
|
1911
1911
|
listEl.innerHTML = '<div class="history-empty">Nothing has happened yet — Command Center will fill this in.</div>';
|
|
1912
1912
|
return;
|
|
1913
1913
|
}
|
|
1914
|
-
// Build the feed as DOM nodes
|
|
1915
|
-
// (
|
|
1914
|
+
// Build the feed as DOM nodes to avoid dynamic .innerHTML
|
|
1915
|
+
// (no-unsanitized/property rule). Each event becomes
|
|
1916
1916
|
// .history-item > .history-head + .history-title + .history-meta, with
|
|
1917
1917
|
// the optional PR "open" link constructed as a real <a> node.
|
|
1918
1918
|
var frag = document.createDocumentFragment();
|
package/docs/deprecated.json
CHANGED
|
@@ -19,10 +19,14 @@
|
|
|
19
19
|
"id": "completion-fallback-parsers",
|
|
20
20
|
"description": "parseStructuredCompletion and parseCompletionFieldSummary in engine/lifecycle.js",
|
|
21
21
|
"file": "engine/lifecycle.js",
|
|
22
|
-
"lines": "
|
|
23
|
-
"telemetryGate": "_engine.completionFallbacks must read 0 across
|
|
22
|
+
"lines": "2856, 3054",
|
|
23
|
+
"telemetryGate": "_engine.completionFallbacks must read 0 (both fenced and summary counters) across sweepWindowDays starting from sweepStartDate",
|
|
24
|
+
"sweepWindowDays": 14,
|
|
25
|
+
"sweepStartDate": "2026-05-27",
|
|
26
|
+
"sweepRationale": "14 days matches the dead-code audit cadence (one full weekly audit cycle plus a buffer week). Policy decision pending confirmation in open-question #1 of the 2026-05-27 Bug Audit Review meeting conclusion — if the human teammate prefers a different cadence, repin this field and the matching enforcingSweepWindowTest fixture.",
|
|
24
27
|
"enforcingTest": "test/unit/completion-fallback-telemetry.test.js:217-234",
|
|
25
|
-
"
|
|
28
|
+
"enforcingSweepWindowTest": "test/unit/completion-fallback-sweep-window.test.js",
|
|
29
|
+
"notes": "Do NOT set removedAt until telemetry confirms zero usage across the sweepWindowDays from sweepStartDate. The follow-up code-removal PR (dropping parseStructuredCompletion at engine/lifecycle.js:2856, parseCompletionFieldSummary at :3054, and the gated fallback at :3852-3877) is dispatched separately once the window is observed clean."
|
|
26
30
|
}
|
|
27
31
|
]
|
|
28
32
|
|
package/engine/cleanup.js
CHANGED
|
@@ -1075,7 +1075,30 @@ async function runCleanup(config, verbose = false) {
|
|
|
1075
1075
|
mutateWorkItems(wiPath, items => {
|
|
1076
1076
|
for (const item of items) if (_migrateLegacyItem(item)) migrated++;
|
|
1077
1077
|
});
|
|
1078
|
-
if (migrated > 0)
|
|
1078
|
+
if (migrated > 0) {
|
|
1079
|
+
log('info', `Migrated ${migrated} legacy status(es) in ${label}`);
|
|
1080
|
+
// P-dcc27d-legacy-status-counter — make the docs/deprecated.json
|
|
1081
|
+
// gate ("legacy-done-aliases" removal after 30 consecutive days zero
|
|
1082
|
+
// migrations) measurable by bumping a rolling-daily counter under
|
|
1083
|
+
// `_engine.legacyStatusMigrations[YYYY-MM-DD]`. Parallel to
|
|
1084
|
+
// `_engine.completionFallbacks` at engine/lifecycle.js:3870-3871.
|
|
1085
|
+
// Inline mutateJsonFileLocked by design — promote to a shared
|
|
1086
|
+
// `mutateMetrics(key, date)` helper if more N-day-silence gates land.
|
|
1087
|
+
try {
|
|
1088
|
+
const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
|
|
1089
|
+
const dateKey = new Date().toISOString().slice(0, 10);
|
|
1090
|
+
mutateJsonFileLocked(metricsPath, (metrics) => {
|
|
1091
|
+
metrics = metrics || {};
|
|
1092
|
+
if (!metrics._engine) metrics._engine = {};
|
|
1093
|
+
if (!metrics._engine.legacyStatusMigrations) metrics._engine.legacyStatusMigrations = {};
|
|
1094
|
+
metrics._engine.legacyStatusMigrations[dateKey] =
|
|
1095
|
+
(metrics._engine.legacyStatusMigrations[dateKey] || 0) + migrated;
|
|
1096
|
+
return metrics;
|
|
1097
|
+
});
|
|
1098
|
+
} catch (err) {
|
|
1099
|
+
log('warn', `telemetry: legacy-status-migration metrics write failed: ${err.message}`);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1079
1102
|
} catch (e) { log('warn', `migrate legacy statuses (${label}): ${e.message}`); }
|
|
1080
1103
|
}
|
|
1081
1104
|
for (const project of projects) _migrateLegacyItemsAt(projectWorkItemsPath(project), `${project.name} work items`);
|
package/engine/lifecycle.js
CHANGED