v2.2.15: Astra Office Refactor & Multi-Service Integration

This commit is contained in:
g1nation
2026-05-16 22:07:06 +09:00
parent 9dcc98ad33
commit 9ca95ab997
46 changed files with 5648 additions and 1299 deletions
+26
View File
@@ -1931,6 +1931,32 @@
/* compact toggle chips kept visible in the top bar (Trace / Web) */
.toggle-chip { font-size: 10.5px; padding: 0 8px; }
/* 스코프 프리셋 segmented control — 기업 모드 chip 옆에 붙임.
세 버튼이 하나의 그룹으로 보이도록 inner border-radius 제거 + 사이 1px 분리.
hidden 속성은 HTML5 기본 inherit, JS 가 toggle. */
.scope-seg { display: inline-flex; align-items: center; gap: 1px; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 5px; background: rgba(255, 255, 255, 0.03); padding: 0; height: 24px; }
.scope-seg[hidden] { display: none; }
.scope-seg-btn {
background: transparent;
color: var(--muted, #94a3b8);
border: 0;
padding: 0 8px;
height: 100%;
font-size: 10.5px;
font-weight: 500;
cursor: pointer;
letter-spacing: 0.02em;
border-radius: 0;
}
.scope-seg-btn:hover { background: rgba(255, 255, 255, 0.05); color: var(--text, #e5e7eb); }
.scope-seg-btn.active {
background: rgba(99, 102, 241, 0.18);
color: #c7d2fe;
font-weight: 700;
}
.scope-seg-btn:first-child { border-top-left-radius: 4px; border-bottom-left-radius: 4px; }
.scope-seg-btn:last-child { border-top-right-radius: 4px; border-bottom-right-radius: 4px; }
/* a trigger + popover menu (Tools ▾ / Edit ▾ / Records ▾) */
.hdr-dropdown { position: relative; display: inline-flex; }
.hdr-menu {
+15
View File
@@ -23,6 +23,21 @@
prompt edits · Knowledge Mix sliders).
-->
<button class="icon-btn toggle-chip" id="companyChip" data-tooltip="1인 기업 모드 — CEO가 요청을 분석해 전문 에이전트에게 분배">기업 모드</button>
<!--
스코프 프리셋 segmented control. 기업 모드 ON 일 때만 표시
(companyChip.active 시 .scope-seg.visible). 각 버튼 = 1개 template:
· 기획만 → plan-only (3 stages)
· 개발까지 → dev-only (10 stages, dev-impl 까지)
· 풀 → full-product-dev (13 stages, 배포까지)
클릭 → setCompanyScopePreset 로 backend stamp + activate. 현재
activePipelineId 가 어느 template 의 suggestedPipelineId 와
일치하느냐로 active 표시 결정.
-->
<div class="scope-seg" id="companyScopeSeg" hidden>
<button class="scope-seg-btn" data-scope="plan-only" data-tooltip="기획서까지만 — 시장조사 → 방향성 → 기획문서 (3단계)">기획만</button>
<button class="scope-seg-btn" data-scope="dev-only" data-tooltip="개발까지만 — 기획 → 설계 → 코드 구현 (10단계, QA·배포 제외)">개발까지</button>
<button class="scope-seg-btn" data-scope="full-product-dev" data-tooltip="풀 파이프라인 — 기획부터 배포까지 (13단계)"></button>
</div>
<button class="icon-btn" id="companyManageBtn" data-tooltip="기업 모드 관리 (에이전트 · 모델 · 프롬프트 · 지식 비중)"></button>
<div class="hdr-dropdown" data-dd>
<button class="icon-btn" id="toolsMenuBtn" data-dd-trigger data-tooltip="개발자 도구 모음">도구 ▾</button>
+77
View File
@@ -945,6 +945,7 @@
case 'companyStatus': {
const v = msg.value || {};
renderCompanyChip(!!v.enabled, v.summary || '');
renderScopeSeg(v.activePipelineId || null);
break;
}
case 'companyIntentDecision': {
@@ -965,11 +966,51 @@
break;
}
case 'pixelOfficeUpdate': {
// 새 path (officeSnapshot) 가 한 번이라도 도착했다면 옛 message 는 무시.
if (window.__officeSnapshotSeen) break;
if (typeof window.__pixelOfficeApply === 'function') {
window.__pixelOfficeApply(msg.value || {});
}
break;
}
case 'officeSnapshot': {
// refactor #E — mini view 도 OfficeSnapshot 수신.
// OfficeSnapshot 을 옛 {state, bubbles, config} payload 모양으로 변환 후
// 기존 __pixelOfficeApply 재사용. dual-mode 안전 전환.
window.__officeSnapshotSeen = true;
const snap = msg.value;
if (!snap || typeof window.__pixelOfficeApply !== 'function') break;
const roster = Array.isArray(snap.roster) ? snap.roster : [];
const active = (snap.activeAgentId && roster.find((a) => a.agentId === snap.activeAgentId)) || roster[0];
const phaseToStatus = (p) => {
if (p === 'awaiting-approval') return 'waiting_approval';
if (p === 'reporting') return 'done';
if (p === 'intake') return 'analyzing';
return p || 'idle';
};
const synthetic = {
agentId: snap.activeAgentId || (active && active.agentId) || 'main',
agentName: (active && active.agentName) || 'Agent',
status: (active && active.status) || phaseToStatus(snap.phase),
currentTask: snap.task && snap.task.goal,
currentStep: active && active.currentStep,
message: snap.activeAgentId || '',
recentLogs: (active && active.lastLog) ? [active.lastLog] : [],
progress: snap.pipeline ? (snap.pipeline.index / Math.max(1, snap.pipeline.stages.length)) : 0,
pipelineStages: snap.pipeline && snap.pipeline.stages,
needUserInput: (snap.awaiting && snap.awaiting.kind === 'clarification') ? snap.awaiting.questions : undefined,
awaitingApproval: (snap.awaiting && snap.awaiting.kind === 'approval') ? snap.awaiting.questions[0] : undefined,
requirementContract: snap.task,
updatedAt: snap.updatedAt,
};
window.__pixelOfficeApply({
state: synthetic,
bubbles: Array.isArray(snap.newBubbles) ? snap.newBubbles : [],
// config 는 그대로 유지 — snapshot 에는 enabled 만 함의적, 옛 cfg 가 살아있음.
config: undefined,
});
break;
}
case 'companyAlignmentCard': {
// Intent Alignment 카드. kind에 따라 4가지 모드:
// - 'auto-proceed' : confidence high → 자동 진행 안내(읽기 전용)
@@ -1843,8 +1884,44 @@
? `1인 기업 ON · ${summary || ''}`.trim()
: '1인 기업 모드 OFF — 클릭해서 켜기',
);
// 스코프 프리셋 segmented control 도 기업 모드 ON 일 때만 노출.
const scopeSeg = document.getElementById('companyScopeSeg');
if (scopeSeg) scopeSeg.hidden = !active;
};
// 활성 pipeline 의 id 가 어느 SCOPE 프리셋의 suggestedPipelineId 와 매칭되는지로 active 표시.
// companyStatus 메시지가 activePipelineId 를 보낼 때마다 호출.
const SCOPE_PRESET_TO_PIPELINE_ID = {
'plan-only': 'plan-only',
'dev-only': 'dev-only',
'full-product-dev': 'product-dev',
};
const renderScopeSeg = (activePipelineId) => {
const scopeSeg = document.getElementById('companyScopeSeg');
if (!scopeSeg) return;
for (const btn of scopeSeg.querySelectorAll('.scope-seg-btn')) {
const tplId = btn.getAttribute('data-scope');
const expected = SCOPE_PRESET_TO_PIPELINE_ID[tplId];
btn.classList.toggle('active', !!activePipelineId && activePipelineId === expected);
}
};
// Wire up clicks once.
const _scopeSeg = document.getElementById('companyScopeSeg');
if (_scopeSeg && !_scopeSeg.dataset.wired) {
_scopeSeg.dataset.wired = '1';
_scopeSeg.addEventListener('click', (e) => {
const btn = e.target && e.target.closest && e.target.closest('.scope-seg-btn');
if (!btn) return;
const tplId = btn.getAttribute('data-scope');
if (!tplId) return;
// Optimistic visual flip — backend ack 가 companyStatus 갱신으로 결과 확정.
for (const b of _scopeSeg.querySelectorAll('.scope-seg-btn')) {
b.classList.toggle('active', b === btn);
}
vscode.postMessage({ type: 'setCompanyScopePreset', templateId: tplId });
});
}
if (_companyChip) {
_companyChip.onclick = () => {
const isActive = _companyChip.classList.contains('active');