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
+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');