release: v2.0.3 - AI 1-Person Company Engine & Business Intelligence

This commit is contained in:
g1nation
2026-05-13 23:22:00 +09:00
parent c40571b7ef
commit b6899851c3
31 changed files with 2504 additions and 41 deletions
+194
View File
@@ -777,6 +777,25 @@
vscode.postMessage({ type: 'getKnowledgeScope', agentPath: msg.selected });
syncContextBar();
break;
case 'companyStatus': {
const v = msg.value || {};
renderCompanyChip(!!v.enabled, v.summary || '');
break;
}
case 'companyAgents': {
renderCompanyAgentCards(msg.value || {});
break;
}
case 'openCompanyManageOverlay': {
// Triggered by the Command Palette `Manage 1인 기업 Agents`.
document.getElementById('companyOverlay')?.classList.add('visible');
vscode.postMessage({ type: 'getCompanyAgents' });
break;
}
case 'companyTurnUpdate': {
if (msg.value) renderCompanyPhase(msg.value);
break;
}
case 'architectureStatus': {
// Show / hide the chip + reflect current state.
const chip = document.getElementById('archChip');
@@ -1350,6 +1369,7 @@
vscode.postMessage({ type: 'getChronicleRecords' });
vscode.postMessage({ type: 'getKnowledgeMix' });
vscode.postMessage({ type: 'getArchitectureStatus' });
vscode.postMessage({ type: 'getCompanyStatus' });
vscode.postMessage({ type: 'ready' });
// ── Project Architecture chip buttons ─────────────────────────────────
@@ -1360,6 +1380,180 @@
if (_archRefreshBtn) _archRefreshBtn.onclick = () => vscode.postMessage({ type: 'refreshArchitecture' });
if (_archDetachBtn) _archDetachBtn.onclick = () => vscode.postMessage({ type: 'detachArchitecture' });
// ── 1인 기업 (Company) Mode chip + manage overlay ─────────────────────
// The chip itself toggles enabled/disabled. The ▾ button opens the
// manage overlay where the user picks active agents + per-agent
// 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 = [
document.getElementById('closeCompanyOverlayBtn'),
document.getElementById('closeCompanyOverlayBtn2'),
].filter(Boolean);
const _companyNameInput = document.getElementById('companyNameInput');
const _saveCompanyNameBtn = document.getElementById('saveCompanyNameBtn');
const _companyAgentList = document.getElementById('companyAgentList');
const _companyStatusEl = document.getElementById('companyStatus');
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) {
_companyChip.onclick = () => {
const isActive = _companyChip.getAttribute('data-active') === 'true';
// Optimistic flip — backend echoes the canonical state back.
renderCompanyChip(!isActive, _companyChipLabel?.textContent || '');
vscode.postMessage({ type: 'setCompanyEnabled', value: !isActive });
};
}
if (_companyManageBtn) {
_companyManageBtn.onclick = () => {
if (!_companyOverlay) return;
_companyOverlay.classList.add('visible');
_companyStatusEl.textContent = '불러오는 중...';
vscode.postMessage({ type: 'getCompanyAgents' });
};
}
for (const btn of _closeCompanyBtns) {
btn.onclick = () => _companyOverlay?.classList.remove('visible');
}
if (_saveCompanyNameBtn && _companyNameInput) {
_saveCompanyNameBtn.onclick = () => {
vscode.postMessage({ type: 'setCompanyName', value: _companyNameInput.value });
};
}
/**
* 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.
*/
function renderCompanyAgentCards(payload) {
if (!_companyAgentList) return;
_companyAgentList.innerHTML = '';
if (_companyNameInput && payload && typeof payload.companyName === 'string') {
_companyNameInput.value = payload.companyName;
}
const agents = (payload && Array.isArray(payload.agents)) ? payload.agents : [];
for (const a of agents) {
const li = document.createElement('li');
li.className = 'company-agent-card';
li.setAttribute('data-active', a.active ? 'true' : 'false');
if (a.alwaysOn) li.setAttribute('data-locked', 'true');
const emoji = document.createElement('span');
emoji.className = 'company-agent-emoji';
emoji.textContent = a.emoji;
const body = document.createElement('div');
body.className = 'company-agent-body';
const name = document.createElement('div');
name.className = 'company-agent-name';
name.innerHTML = `${escAttr(a.name)} <span class="company-agent-role">${escAttr(a.role)}</span>`;
const tag = document.createElement('div');
tag.className = 'company-agent-tagline';
tag.textContent = a.tagline || '';
tag.title = a.specialty || '';
body.appendChild(name);
body.appendChild(tag);
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 = () => {
vscode.postMessage({
type: 'setCompanyAgentModel',
agentId: a.id,
model: modelInput.value.trim(),
});
};
const toggle = document.createElement('button');
toggle.className = 'company-agent-toggle';
toggle.textContent = a.active ? 'ON' : 'OFF';
if (a.alwaysOn) {
toggle.disabled = true;
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';
const nextIds = Array.from(_companyAgentList.querySelectorAll('.company-agent-card'))
.filter(el => el.getAttribute('data-active') === 'true')
.map(el => el.dataset.agentId)
.filter(Boolean);
vscode.postMessage({ type: 'setCompanyActiveAgents', value: nextIds });
};
}
li.dataset.agentId = a.id;
controls.appendChild(modelInput);
controls.appendChild(toggle);
li.appendChild(emoji);
li.appendChild(body);
li.appendChild(controls);
_companyAgentList.appendChild(li);
}
if (_companyStatusEl) _companyStatusEl.textContent = '';
}
/**
* Render one phase event from the dispatcher. The chat gets a
* card per phase so the user can follow progress in real time —
* "🧭 CEO 작업 분배 중..." → "📺 레오 작업 수행 중..." → final report.
*/
function renderCompanyPhase(ev) {
const chatEl = document.getElementById('chat');
if (!chatEl) return;
const card = document.createElement('div');
card.className = 'company-phase-card';
if (ev.phase === 'plan-start') {
card.innerHTML = '<div class="cph-head">🧭 CEO</div><div class="cph-meta">작업 분배 중…</div>';
} else if (ev.phase === 'plan-ready') {
const tasks = (ev.plan?.tasks || []).map((t, i) => `${i + 1}. <strong>${escAttr(t.agent)}</strong> — ${escAttr(t.task)}`).join('<br>');
card.innerHTML = `<div class="cph-head">🧭 CEO 브리프</div>
<div>${escAttr(ev.plan?.brief || '(brief 없음)')}</div>
<div class="cph-meta" style="margin-top:6px">${tasks || '(no tasks — chat reply)'}</div>`;
} else if (ev.phase === 'agent-start') {
card.innerHTML = `<div class="cph-head">${escAttr(ev.agentId)} 작업 수행 중…</div>
<div class="cph-meta">${escAttr(ev.task)} <em>(${ev.index + 1}/${ev.total})</em></div>`;
} else if (ev.phase === 'agent-done') {
const o = ev.output || {};
const body = (o.response || '').slice(0, 4000);
card.innerHTML = `<div class="cph-head">${escAttr(ev.agentId)} 완료 <span class="cph-meta">${(o.durationMs/1000).toFixed(1)}s${o.error ? ' · ⚠️ ' + escAttr(o.error) : ''}</span></div>
<div class="markdown-body">${fmt(body)}</div>`;
} else if (ev.phase === 'report-start') {
card.innerHTML = '<div class="cph-head">🧭 CEO 종합 보고서 작성 중…</div>';
} else if (ev.phase === 'report-done') {
card.className += ' report';
card.innerHTML = `<div class="cph-head">🧭 CEO 보고서${ev.ok ? '' : ' (fallback)'}</div>
<div class="markdown-body">${fmt(ev.report || '')}</div>`;
} else if (ev.phase === 'session-saved') {
card.innerHTML = `<div class="cph-meta">세션 저장 완료 — 클릭하여 열기</div>`;
card.style.cursor = 'pointer';
card.onclick = () => vscode.postMessage({ type: 'openCompanySession', sessionDir: ev.sessionDir });
} else if (ev.phase === 'aborted') {
card.innerHTML = `<div class="cph-head">⛔ 회사 모드 중단</div><div class="cph-meta">${escAttr(ev.reason)}</div>`;
}
chatEl.appendChild(card);
chatEl.scrollTop = chatEl.scrollHeight;
}
// ── Knowledge Mix: global slider ──────────────────────────────────────
// Mirrors `g1nation.knowledgeMix.secondBrainWeight`. The hint label updates
// live as the user drags; the value is committed (postMessage) on `change`