Release v2.1.8: Company Agent roster overhaul and UI polish

This commit is contained in:
g1nation
2026-05-14 22:25:48 +09:00
parent ac0085ab53
commit 6b10d002fa
21 changed files with 674 additions and 331 deletions
+94 -22
View File
@@ -179,7 +179,7 @@
const recordsLatest = document.getElementById('recordsLatest');
function escAttr(t) { return String(t == null ? '' : t).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); }
function fmtMixHint(w) { return `Model ${100 - w}% · Brain ${w}%`; }
function fmtMixHint(w) { return `모델 ${100 - w}% · 두뇌 ${w}%`; }
/** Compact relative-time formatter for chips (e.g. "2m ago", "just now"). */
function formatRelativeTime(iso) {
try {
@@ -405,7 +405,7 @@
: Math.max(0, Math.min(100, agentMapDraft.secondBrainWeight | 0));
slider.value = String(value);
hint.textContent = useGlobal
? `Use global · ${fmtMixHint(value)}`
? `전역 설정 사용 · ${fmtMixHint(value)}`
: fmtMixHint(value);
}
@@ -815,12 +815,24 @@
case 'deleteCompanyAgentResult': {
const v = msg.value || {};
if (v.ok) {
showToast(`🗑 '${v.agentId}' 에이전트 삭제됨`, 'warn');
const msg2 = v.kind === 'builtin-hidden'
? `🗑 기본 에이전트 '${v.agentId}' 숨김 처리 (목록 하단에서 복원 가능)`
: `🗑 '${v.agentId}' 에이전트 삭제됨`;
showToast(msg2, 'warn');
} else {
showToast(`삭제 실패: ${v.reason || '알 수 없음'}`, 'warn');
}
break;
}
case 'restoreHiddenAgentResult': {
const v = msg.value || {};
if (v.ok) {
showToast(`↩ '${v.agentId}' 에이전트 복원 완료`, 'info');
} else {
showToast(`복원 실패: ${v.reason || '알 수 없음'}`, 'warn');
}
break;
}
case 'companyPipelines': {
if (typeof window.__renderCompanyPipelines === 'function') {
window.__renderCompanyPipelines(msg.value || {});
@@ -2279,7 +2291,24 @@
}
if (payload && payload.roleCategoryLabels) _roleCategoryLabels = payload.roleCategoryLabels;
if (payload && Array.isArray(payload.roleCategoryOrder)) _roleCategoryOrder = payload.roleCategoryOrder;
const agents = (payload && Array.isArray(payload.agents)) ? payload.agents : [];
const rawAgents = (payload && Array.isArray(payload.agents)) ? payload.agents : [];
// 활성/비활성 정렬: CEO는 항상 최상단(alwaysOn), 그 다음 활성(켜짐) 에이전트,
// 마지막으로 비활성. 같은 그룹 내에서는 백엔드가 보낸 원래 순서를 유지(stable).
const agents = rawAgents
.map((a, i) => ({ a, i }))
.sort((x, y) => {
// CEO/alwaysOn 최우선
const xLocked = x.a.alwaysOn ? 0 : 1;
const yLocked = y.a.alwaysOn ? 0 : 1;
if (xLocked !== yLocked) return xLocked - yLocked;
// 그 다음 active 여부
const xAct = x.a.active ? 0 : 1;
const yAct = y.a.active ? 0 : 1;
if (xAct !== yAct) return xAct - yAct;
// tiebreak: 원래 순서
return x.i - y.i;
})
.map((p) => p.a);
for (const a of agents) {
const li = document.createElement('li');
li.className = 'company-agent-card';
@@ -2358,10 +2387,10 @@
const editBtn = document.createElement('button');
editBtn.className = 'company-agent-edit';
editBtn.textContent = '✎ Edit';
editBtn.textContent = '✎ 편집';
if (a.personaOverridden || a.specialtyOverridden || a.taglineOverridden) {
editBtn.classList.add('dirty');
editBtn.title = 'prompt 편집됨 (원본과 다름)';
editBtn.title = '프롬프트가 사용자에 의해 편집되었습니다 (원본과 다름)';
}
editBtn.onclick = () => {
const expanded = li.getAttribute('data-expanded') === 'true';
@@ -2370,15 +2399,15 @@
const toggle = document.createElement('button');
toggle.className = 'company-agent-toggle';
toggle.textContent = a.active ? 'ON' : 'OFF';
toggle.textContent = a.active ? '켜짐' : '꺼짐';
if (a.alwaysOn) {
toggle.disabled = true;
toggle.textContent = 'LOCKED';
toggle.textContent = '고정';
} else {
toggle.onclick = () => {
const wantActive = !(li.getAttribute('data-active') === 'true');
li.setAttribute('data-active', wantActive ? 'true' : 'false');
toggle.textContent = wantActive ? 'ON' : 'OFF';
toggle.textContent = wantActive ? '켜짐' : '꺼짐';
const nextIds = Array.from(_companyAgentList.querySelectorAll('.company-agent-card'))
.filter(el => el.getAttribute('data-active') === 'true')
.map(el => el.dataset.agentId)
@@ -2389,17 +2418,30 @@
controls.appendChild(roleSelEl);
controls.appendChild(modelSelEl);
controls.appendChild(editBtn);
// 사용자 추가 에이전트만 삭제 가능. 기본 9명은 코드에 박혀 있어
// 백엔드도 거부하므로 UI에서도 버튼을 노출하지 않음.
if (a.custom) {
// 삭제 버튼 — CEO만 빼고 빌트인/커스텀 모두 노출. 단, 어떤 워크 파이프라인의
// stage라도 이 에이전트를 참조하고 있으면 disabled로 처리하고 tooltip에
// 사용 중인 파이프라인을 적어, 사용자가 어디로 가서 빼야 하는지 보이게 한다.
if (!a.alwaysOn) {
const delBtn = document.createElement('button');
delBtn.className = 'company-agent-edit';
delBtn.className = 'company-agent-edit company-agent-delete';
delBtn.textContent = '🗑';
delBtn.title = `'${a.name}' 에이전트 삭제`;
delBtn.onclick = () => {
if (!confirm(`'${a.name}' 에이전트를 삭제할까요? 이 에이전트의 모든 설정(모델·프롬프트·지식 믹스)도 함께 삭제됩니다.`)) return;
vscode.postMessage({ type: 'deleteCompanyAgent', agentId: a.id });
};
const usedIn = Array.isArray(a.usedInPipelines) ? a.usedInPipelines : [];
if (usedIn.length > 0) {
delBtn.disabled = true;
delBtn.classList.add('disabled');
delBtn.title = `삭제 불가 — 다음 파이프라인에서 사용 중: ${usedIn.map((n) => `'${n}'`).join(', ')}. 파이프라인에서 먼저 빼거나 다른 에이전트로 교체하세요.`;
} else {
delBtn.title = a.custom
? `'${a.name}' 에이전트 삭제 (모든 설정 함께 제거)`
: `'${a.name}' 에이전트 숨기기 (기본 에이전트라 복원 가능)`;
delBtn.onclick = () => {
const confirmMsg = a.custom
? `'${a.name}' 에이전트를 삭제할까요? 이 에이전트의 모든 설정(모델·프롬프트·지식 비중)도 함께 삭제됩니다.`
: `기본 에이전트 '${a.name}'을(를) 목록에서 숨길까요? 나중에 [목록 맨 아래 → 삭제된 기본 에이전트] 영역에서 복원할 수 있습니다.`;
if (!confirm(confirmMsg)) return;
vscode.postMessage({ type: 'deleteCompanyAgent', agentId: a.id });
};
}
controls.appendChild(delBtn);
}
controls.appendChild(toggle);
@@ -2422,6 +2464,36 @@
li.appendChild(_buildAgentPromptEditor(a));
_companyAgentList.appendChild(li);
}
// ── 숨겨진 기본 에이전트 복원 영역 ──
// 사용자가 "삭제"한 빌트인 에이전트를 다시 꺼낼 수 있는 출구. 빈 배열이면
// 섹션 자체를 그리지 않아 평소엔 시야에서 사라진다.
const hidden = Array.isArray(payload && payload.hiddenBuiltins) ? payload.hiddenBuiltins : [];
if (hidden.length > 0) {
const restoreLi = document.createElement('li');
restoreLi.className = 'company-agent-hidden-section';
const head = document.createElement('div');
head.className = 'company-agent-hidden-head';
head.textContent = `삭제된 기본 에이전트 (${hidden.length}명)`;
restoreLi.appendChild(head);
const hint = document.createElement('div');
hint.className = 'company-agent-hidden-hint';
hint.textContent = '복원하면 목록에 다시 추가됩니다. 설정은 모두 디폴트로 시작합니다.';
restoreLi.appendChild(hint);
const list = document.createElement('div');
list.className = 'company-agent-hidden-list';
for (const h of hidden) {
const chip = document.createElement('button');
chip.className = 'company-agent-hidden-chip';
chip.textContent = `${h.emoji || '🙂'} ${h.name} · ${h.role} ↩ 복원`;
chip.title = `'${h.name}' 복원`;
chip.onclick = () => {
vscode.postMessage({ type: 'restoreHiddenAgent', agentId: h.id });
};
list.appendChild(chip);
}
restoreLi.appendChild(list);
_companyAgentList.appendChild(restoreLi);
}
if (_companyStatusEl) _companyStatusEl.textContent = '';
}
@@ -2573,15 +2645,15 @@
const resetBtn = document.createElement('button');
resetBtn.className = 'danger';
resetBtn.textContent = 'Reset';
resetBtn.title = '이 에이전트의 모든 override 제거 → 디폴트로 복귀 (이름·역할·프롬프트 전부)';
resetBtn.textContent = '기본값으로';
resetBtn.title = '이 에이전트의 모든 사용자 변경 사항 제거 → 디폴트로 복귀 (이름·역할·프롬프트 전부)';
resetBtn.onclick = () => {
vscode.postMessage({ type: 'setCompanyAgentPrompt', agentId: a.id, override: null });
vscode.postMessage({ type: 'setCompanyAgentDisplay', agentId: a.id, override: null });
};
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.textContent = '취소';
cancelBtn.onclick = () => {
const card = editor.closest('.company-agent-card');
if (card) card.setAttribute('data-expanded', 'false');
@@ -2589,7 +2661,7 @@
const saveBtn = document.createElement('button');
saveBtn.className = 'primary';
saveBtn.textContent = 'Save';
saveBtn.textContent = '저장';
saveBtn.onclick = () => {
// 두 개의 message로 분리 전송 — display(name/role/emoji/color)와
// prompt(tagline/specialty/persona)는 백엔드에서 서로 다른