release: v2.0.4 - Advanced Business Orchestration & UI Polishing

This commit is contained in:
g1nation
2026-05-13 23:32:29 +09:00
parent b6899851c3
commit 6784e85b7e
15 changed files with 417 additions and 45 deletions
+60 -1
View File
@@ -390,8 +390,67 @@
.company-agent-model {
background: var(--input-bg); border: 1px solid var(--border);
color: var(--text-primary); font-size: 10px;
padding: 3px 6px; border-radius: 6px; max-width: 130px;
padding: 3px 6px; border-radius: 6px;
max-width: 150px; min-width: 0;
cursor: pointer;
}
.company-agent-model option { color: var(--text-primary); background: var(--bg); }
.company-agent-edit {
background: transparent; border: 1px solid var(--border);
color: var(--text-dim); font-size: 10px;
padding: 3px 6px; border-radius: 6px; cursor: pointer;
flex-shrink: 0;
}
.company-agent-edit:hover { color: var(--accent); border-color: var(--accent); }
.company-agent-edit.dirty {
color: var(--accent); border-color: var(--accent);
background: var(--accent-glow);
}
/* Expandable prompt editor under each agent card. Toggled via the
Edit button. Three textareas (tagline / specialty / persona) +
Reset / Save / Cancel — empty save clears that field's override. */
.company-agent-editor {
display: none;
margin: 6px 0 0 38px; /* indent under the emoji */
padding: 8px;
background: var(--bg-secondary);
border: 1px dashed var(--border);
border-radius: 6px;
}
.company-agent-card[data-expanded="true"] .company-agent-editor { display: block; }
.company-agent-editor .field-label {
display: flex; justify-content: space-between; align-items: center;
margin-top: 6px; font-size: 10px; color: var(--text-dim);
text-transform: uppercase; letter-spacing: 0.04em;
}
.company-agent-editor .field-label:first-child { margin-top: 0; }
.company-agent-editor .field-label .field-flag {
text-transform: none; letter-spacing: 0;
color: var(--accent); font-size: 9.5px;
}
.company-agent-editor input[type="text"],
.company-agent-editor textarea {
width: 100%; box-sizing: border-box;
background: var(--input-bg); color: var(--text-primary);
border: 1px solid var(--border); border-radius: 4px;
padding: 6px 8px; font-size: 11px; font-family: inherit;
margin-top: 3px;
}
.company-agent-editor textarea { resize: vertical; min-height: 60px; }
.company-agent-editor .editor-actions {
display: flex; justify-content: flex-end; gap: 6px; margin-top: 8px;
}
.company-agent-editor .editor-actions button {
font-size: 10px; padding: 4px 10px; border-radius: 5px; cursor: pointer;
background: var(--surface); color: var(--text-primary);
border: 1px solid var(--border);
}
.company-agent-editor .editor-actions button.primary {
background: var(--accent); border-color: var(--accent); color: #fff;
}
.company-agent-editor .editor-actions button.danger { color: var(--error); }
.company-agent-editor .editor-actions button:hover { border-color: var(--border-bright); }
/* Per-phase company turn header in chat. */
.company-phase-card {
+168 -18
View File
@@ -696,6 +696,13 @@
statusLabel.innerText = '';
// Refresh per-agent model dropdown options (if currently visible) so it stays in sync.
if (typeof refreshAgentMapModelOptions === 'function') refreshAgentMapModelOptions();
// If the company manage overlay is open with cached agent data,
// re-render its cards so each per-agent model <select> picks up
// any newly-discovered models from the master list.
if (typeof _lastCompanyAgentsPayload !== 'undefined' && _lastCompanyAgentsPayload
&& document.getElementById('companyOverlay')?.classList.contains('visible')) {
renderCompanyAgentCards(_lastCompanyAgentsPayload);
}
break;
}
case 'brainProfiles':
@@ -1430,12 +1437,53 @@
}
/**
* Render the agent cards in the manage overlay. Each card has a
* toggle (active on/off) and a model input (per-agent override).
* CEO is rendered but locked-on; clicking its toggle is a no-op.
* Keep the last payload around so we can re-render whenever the
* model list refreshes (the top `#modelSel` is the source of truth
* for available models — see `populateAgentModelSelect`).
*/
let _lastCompanyAgentsPayload = null;
/**
* Populate one agent's model `<select>` from the master `#modelSel`
* options. Mirrors the pattern used by `refreshAgentMapModelOptions`
* so the user picks from the same canonical list everywhere.
* Empty value = "default (global)". Saved overrides not in the list
* are preserved as a "(saved)" option so the value never gets lost.
*/
function populateAgentModelSelect(sel, current) {
sel.innerHTML = '';
const useDefault = document.createElement('option');
useDefault.value = '';
useDefault.innerText = 'default (global)';
sel.appendChild(useDefault);
const seen = new Set();
for (const opt of modelSel.options) {
if (!opt.value || seen.has(opt.value)) continue;
seen.add(opt.value);
const o = document.createElement('option');
o.value = opt.value;
o.innerText = opt.innerText;
sel.appendChild(o);
}
if (current && !seen.has(current)) {
const o = document.createElement('option');
o.value = current;
o.innerText = `${current} (saved)`;
sel.appendChild(o);
}
sel.value = current || '';
}
/**
* Render the agent cards in the manage overlay. Each card has:
* - a model dropdown (default + every loaded model)
* - an ON/OFF toggle (CEO always-on)
* - an Edit button that toggles an inline prompt editor with
* tagline / specialty / persona textareas + Reset/Save/Cancel.
*/
function renderCompanyAgentCards(payload) {
if (!_companyAgentList) return;
_lastCompanyAgentsPayload = payload;
_companyAgentList.innerHTML = '';
if (_companyNameInput && payload && typeof payload.companyName === 'string') {
_companyNameInput.value = payload.companyName;
@@ -1446,6 +1494,14 @@
li.className = 'company-agent-card';
li.setAttribute('data-active', a.active ? 'true' : 'false');
if (a.alwaysOn) li.setAttribute('data-locked', 'true');
li.dataset.agentId = a.id;
// ── Row 1: emoji + name/tagline + controls ──
const row = document.createElement('div');
row.style.display = 'flex';
row.style.alignItems = 'center';
row.style.gap = '10px';
row.style.width = '100%';
const emoji = document.createElement('span');
emoji.className = 'company-agent-emoji';
@@ -1466,20 +1522,30 @@
const controls = document.createElement('div');
controls.className = 'company-agent-controls';
const modelInput = document.createElement('input');
modelInput.type = 'text';
modelInput.className = 'company-agent-model';
modelInput.placeholder = 'default';
modelInput.value = a.modelOverride || '';
modelInput.title = '비워두면 글로벌 기본 모델 사용';
modelInput.onchange = () => {
const modelSelEl = document.createElement('select');
modelSelEl.className = 'company-agent-model';
modelSelEl.title = '비워두면 글로벌 기본 모델 사용';
populateAgentModelSelect(modelSelEl, a.modelOverride || '');
modelSelEl.onchange = () => {
vscode.postMessage({
type: 'setCompanyAgentModel',
agentId: a.id,
model: modelInput.value.trim(),
model: modelSelEl.value || '',
});
};
const editBtn = document.createElement('button');
editBtn.className = 'company-agent-edit';
editBtn.textContent = '✎ Edit';
if (a.personaOverridden || a.specialtyOverridden || a.taglineOverridden) {
editBtn.classList.add('dirty');
editBtn.title = 'prompt 편집됨 (원본과 다름)';
}
editBtn.onclick = () => {
const expanded = li.getAttribute('data-expanded') === 'true';
li.setAttribute('data-expanded', expanded ? 'false' : 'true');
};
const toggle = document.createElement('button');
toggle.className = 'company-agent-toggle';
toggle.textContent = a.active ? 'ON' : 'OFF';
@@ -1488,8 +1554,6 @@
toggle.textContent = 'LOCKED';
} else {
toggle.onclick = () => {
// Optimistic update + send the full new list so the
// backend has a single canonical replace operation.
const wantActive = !(li.getAttribute('data-active') === 'true');
li.setAttribute('data-active', wantActive ? 'true' : 'false');
toggle.textContent = wantActive ? 'ON' : 'OFF';
@@ -1500,18 +1564,104 @@
vscode.postMessage({ type: 'setCompanyActiveAgents', value: nextIds });
};
}
li.dataset.agentId = a.id;
controls.appendChild(modelInput);
controls.appendChild(modelSelEl);
controls.appendChild(editBtn);
controls.appendChild(toggle);
li.appendChild(emoji);
li.appendChild(body);
li.appendChild(controls);
row.appendChild(emoji);
row.appendChild(body);
row.appendChild(controls);
li.appendChild(row);
// ── Row 2 (collapsed by default): prompt editor ──
li.appendChild(_buildAgentPromptEditor(a));
_companyAgentList.appendChild(li);
}
if (_companyStatusEl) _companyStatusEl.textContent = '';
}
/**
* Build the per-agent prompt editor. Hidden until the user clicks
* the Edit button. Three fields (tagline / specialty / persona);
* Save sends whichever fields actually changed; Reset sends `null`
* to wipe all overrides at once.
*/
function _buildAgentPromptEditor(a) {
const editor = document.createElement('div');
editor.className = 'company-agent-editor';
const _field = (key, labelText, isTextarea, current, defaultVal, overridden) => {
const lbl = document.createElement('label');
lbl.className = 'field-label';
lbl.innerHTML = `<span>${labelText}</span>` +
(overridden
? '<span class="field-flag">overridden</span>'
: '<span class="field-flag" style="color:var(--text-dim)">default</span>');
editor.appendChild(lbl);
const el = isTextarea
? document.createElement('textarea')
: document.createElement('input');
if (!isTextarea) el.type = 'text';
el.value = current || '';
el.placeholder = defaultVal || '';
editor.appendChild(el);
return el;
};
const tagInput = _field('tagline', 'Tagline (한 줄)', false, a.tagline, a.defaultTagline, a.taglineOverridden);
const specInput = _field('specialty', 'Specialty (CEO가 dispatch 판단에 사용)', true, a.specialty, a.defaultSpecialty, a.specialtyOverridden);
const persInput = _field('persona', 'Persona (말투·관점·강조)', true, a.persona, a.defaultPersona, a.personaOverridden);
specInput.rows = 3;
persInput.rows = 5;
const actions = document.createElement('div');
actions.className = 'editor-actions';
const resetBtn = document.createElement('button');
resetBtn.className = 'danger';
resetBtn.textContent = 'Reset';
resetBtn.title = '이 에이전트의 모든 override 제거 → 디폴트로 복귀';
resetBtn.onclick = () => {
vscode.postMessage({
type: 'setCompanyAgentPrompt',
agentId: a.id,
override: null,
});
};
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.onclick = () => {
const card = editor.closest('.company-agent-card');
if (card) card.setAttribute('data-expanded', 'false');
};
const saveBtn = document.createElement('button');
saveBtn.className = 'primary';
saveBtn.textContent = 'Save';
saveBtn.onclick = () => {
// Send what's currently in each field. The backend treats an
// empty string as "clear this field" (back to default), so
// typing nothing into Tagline + saving = Tagline default,
// Specialty + Persona untouched if not modified.
vscode.postMessage({
type: 'setCompanyAgentPrompt',
agentId: a.id,
override: {
tagline: tagInput.value === a.defaultTagline ? '' : tagInput.value,
specialty: specInput.value === a.defaultSpecialty ? '' : specInput.value,
persona: persInput.value === a.defaultPersona ? '' : persInput.value,
},
});
};
actions.appendChild(resetBtn);
actions.appendChild(cancelBtn);
actions.appendChild(saveBtn);
editor.appendChild(actions);
return editor;
}
/**
* Render one phase event from the dispatcher. The chat gets a
* card per phase so the user can follow progress in real time —