release: v2.0.8 - UX Persistence & Per-Agent Knowledge Mix (2026-05-14)

This commit is contained in:
g1nation
2026-05-14 00:56:20 +09:00
parent 8104caf8d9
commit 147536fb13
19 changed files with 423 additions and 55 deletions
+31 -20
View File
@@ -321,26 +321,10 @@
.input-footer { display: flex; align-items: center; justify-content: space-between; }
.footer-left { display: flex; align-items: center; gap: 8px; }
/* Company chip — sits in the records-line beside the Records ▾ menu. */
.company-chip {
display: inline-flex; align-items: center; gap: 5px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 999px;
padding: 3px 10px;
color: var(--text-dim);
font-size: 11px; font-weight: 600;
cursor: pointer;
transition: all 0.15s ease;
}
.company-chip:hover { border-color: var(--border-bright); color: var(--text-primary); }
.company-chip[data-active="true"] {
background: var(--accent-glow);
border-color: var(--accent);
color: var(--accent);
}
.company-chip-icon { font-size: 12px; }
.company-manage-btn { padding: 2px 6px; font-size: 11px; margin-left: 2px; }
/* (Removed) Pill-shaped `.company-chip` / `.company-manage-btn` —
the Corp toggle now lives in the header toolbar reusing the
existing `.icon-btn.toggle-chip` rounded-rectangle look. See
#companyChip in sidebar.html. */
.company-name-input {
flex: 1; background: var(--input-bg); border: 1px solid var(--border);
border-radius: 6px; padding: 6px 10px; color: var(--text-primary); font-size: 12px;
@@ -407,6 +391,33 @@
background: var(--accent-glow);
}
/* Per-agent Knowledge Mix slider row — sits between the controls
row and the (collapsed) prompt editor. Indent matches the
prompt-editor indent (38px) so the emoji stays as the visual
"ruler" for everything that follows. */
.company-agent-mix-row {
display: flex; align-items: center; gap: 8px;
margin: 6px 0 0 38px;
font-size: 10px; color: var(--text-dim);
}
.company-agent-mix-label { flex-shrink: 0; }
.company-agent-mix-slider {
flex: 1; min-width: 0;
accent-color: var(--accent);
cursor: pointer;
}
.company-agent-mix-slider:disabled { opacity: 0.5; cursor: not-allowed; }
.company-agent-mix-hint {
flex-shrink: 0; font-size: 9.5px; color: var(--text-dim);
min-width: 165px; text-align: right;
}
.company-agent-mix-cbwrap {
display: inline-flex; align-items: center; gap: 3px;
font-size: 9.5px; cursor: pointer; color: var(--text-dim);
flex-shrink: 0;
}
.company-agent-mix-cbwrap input[type="checkbox"] { accent-color: var(--accent); }
/* 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. */
+11 -10
View File
@@ -15,6 +15,15 @@
<button class="icon-btn" id="newChatBtn" data-tooltip="New Chat">New</button>
<button class="icon-btn toggle-chip active" id="brainTraceBtn" data-tooltip="Second Brain Trace Mode">Trace</button>
<button class="icon-btn toggle-chip" id="internetBtn" data-tooltip="Internet Access">Web</button>
<!--
Corp toggle — replaces the old pill-shaped chip that lived
in the records-line. Same rounded-rectangle style as the
other header buttons. Clicking toggles 1인 기업 모드 on/off;
the adjacent ▾ opens the manage overlay (agents · models ·
prompt edits · Knowledge Mix sliders).
-->
<button class="icon-btn toggle-chip" id="companyChip" data-tooltip="1인 기업 모드 토글">Corp</button>
<button class="icon-btn" id="companyManageBtn" data-tooltip="회사 관리 (에이전트·모델·prompt·Knowledge Mix)"></button>
<div class="hdr-dropdown" data-dd>
<button class="icon-btn" id="toolsMenuBtn" data-dd-trigger data-tooltip="Tools">Tools ▾</button>
<div class="hdr-menu" id="toolsMenu" data-dd-menu>
@@ -106,16 +115,8 @@
<span id="chronicleAutoStatus" title="Project records are saved automatically after meaningful project turns.">Auto Records</span>
<span class="rl-latest" id="recordsLatest"></span>
</div>
<!--
Company-mode chip. Click toggles enabled; the ▾ opens the manage
overlay. The chip stays visible at all times so the user can flip
into 1인 기업 mode from anywhere in the chat surface.
-->
<button class="company-chip" id="companyChip" data-active="false" title="1인 기업 모드 토글">
<span class="company-chip-icon">🏢</span>
<span class="company-chip-label" id="companyChipLabel">Company OFF</span>
</button>
<button class="icon-btn company-manage-btn" id="companyManageBtn" data-tooltip="회사 관리 (에이전트·모델 설정)"></button>
<!-- (Removed) Corp chip moved to the header toolbar above —
see #companyChip / #companyManageBtn alongside New/Trace/Web. -->
<div class="hdr-dropdown" data-dd>
<button class="icon-btn" id="recordsMenuBtn" data-dd-trigger data-tooltip="Chronicle records">Records ▾</button>
<div class="hdr-menu hdr-menu-wide" id="recordsMenu" data-dd-menu>
+99 -6
View File
@@ -1432,7 +1432,6 @@
// model overrides. State round-trips through `companyStatus` /
// `companyAgents` messages so the webview and extension stay in sync.
const _companyChip = document.getElementById('companyChip');
const _companyChipLabel = document.getElementById('companyChipLabel');
const _companyManageBtn = document.getElementById('companyManageBtn');
const _companyOverlay = document.getElementById('companyOverlay');
const _closeCompanyBtns = [
@@ -1444,17 +1443,29 @@
const _companyAgentList = document.getElementById('companyAgentList');
const _companyStatusEl = document.getElementById('companyStatus');
/**
* Chip lives in the main header toolbar now, so it uses the same
* `icon-btn.active` styling as `brainTraceBtn` / `internetBtn`.
* Detail (company name + agent count) goes in the tooltip — the
* label stays a constant "Corp" so the toolbar tone-and-manner
* isn't broken by a wildly varying-width chip.
*/
const renderCompanyChip = (active, summary) => {
if (!_companyChip || !_companyChipLabel) return;
_companyChip.setAttribute('data-active', active ? 'true' : 'false');
_companyChipLabel.textContent = active ? (summary || 'Company ON') : 'Company OFF';
if (!_companyChip) return;
_companyChip.classList.toggle('active', !!active);
_companyChip.setAttribute(
'data-tooltip',
active
? `1인 기업 ON · ${summary || ''}`.trim()
: '1인 기업 모드 OFF — 클릭해서 켜기',
);
};
if (_companyChip) {
_companyChip.onclick = () => {
const isActive = _companyChip.getAttribute('data-active') === 'true';
const isActive = _companyChip.classList.contains('active');
// Optimistic flip — backend echoes the canonical state back.
renderCompanyChip(!isActive, _companyChipLabel?.textContent || '');
renderCompanyChip(!isActive, '');
vscode.postMessage({ type: 'setCompanyEnabled', value: !isActive });
};
}
@@ -1612,6 +1623,15 @@
row.appendChild(controls);
li.appendChild(row);
// ── Row 1.5: per-agent Knowledge Mix slider ──
// CEO doesn't dispatch agents itself, it only synthesises,
// so the brain mix for CEO turns is governed by the
// *specialist* it dispatched — exposing the slider for CEO
// would just be a confusing dead control.
if (a.id !== 'ceo') {
li.appendChild(_buildAgentKnowledgeMixSlider(a, payload.globalKnowledgeMixWeight));
}
// ── Row 2 (collapsed by default): prompt editor ──
li.appendChild(_buildAgentPromptEditor(a));
_companyAgentList.appendChild(li);
@@ -1619,6 +1639,79 @@
if (_companyStatusEl) _companyStatusEl.textContent = '';
}
/**
* Inline Knowledge Mix slider for one agent. Empty (override = null)
* means "use the global slider value" — shown as a hint label so the
* user knows what they'll fall back to. Slider commits on `change`
* (not `input`) so dragging doesn't spam writes.
*/
function _buildAgentKnowledgeMixSlider(a, globalWeight) {
const row = document.createElement('div');
row.className = 'company-agent-mix-row';
const usingOverride = a.knowledgeMixOverride !== null && a.knowledgeMixOverride !== undefined;
const effective = a.effectiveKnowledgeMixWeight;
const label = document.createElement('span');
label.className = 'company-agent-mix-label';
label.textContent = '🎚 Knowledge Mix';
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '0'; slider.max = '100'; slider.step = '5';
slider.value = String(effective);
slider.disabled = !usingOverride;
slider.className = 'company-agent-mix-slider';
const hint = document.createElement('span');
hint.className = 'company-agent-mix-hint';
const renderHint = () => {
const w = parseInt(slider.value, 10) || 50;
const tag = usingOverride
? `override · Model ${100 - w}% / Brain ${w}%`
: `global · Model ${100 - effective}% / Brain ${effective}%`;
hint.textContent = tag;
};
const cb = document.createElement('input');
cb.type = 'checkbox'; cb.checked = !usingOverride;
cb.className = 'company-agent-mix-cb';
cb.title = '글로벌 슬라이더 값 사용';
cb.onchange = () => {
if (cb.checked) {
// Reset to global.
slider.disabled = true;
slider.value = String(globalWeight ?? 50);
vscode.postMessage({
type: 'setCompanyAgentKnowledgeMix',
agentId: a.id, value: null,
});
} else {
// Take ownership at the current displayed value.
slider.disabled = false;
const w = parseInt(slider.value, 10) || 50;
vscode.postMessage({
type: 'setCompanyAgentKnowledgeMix',
agentId: a.id, value: w,
});
}
};
slider.addEventListener('input', renderHint);
slider.addEventListener('change', () => {
if (cb.checked) return; // safety: shouldn't fire when disabled
const w = parseInt(slider.value, 10) || 50;
vscode.postMessage({
type: 'setCompanyAgentKnowledgeMix',
agentId: a.id, value: w,
});
});
renderHint();
const cbWrap = document.createElement('label');
cbWrap.className = 'company-agent-mix-cbwrap';
cbWrap.appendChild(cb);
cbWrap.appendChild(document.createTextNode(' use global'));
row.appendChild(label);
row.appendChild(slider);
row.appendChild(hint);
row.appendChild(cbWrap);
return row;
}
/**
* Build the per-agent prompt editor. Hidden until the user clicks
* the Edit button. Three fields (tagline / specialty / persona);