Release v2.2.1: Autonomous Task Resumption & Engine Resilience

This commit is contained in:
g1nation
2026-05-14 23:27:51 +09:00
parent e86e3177c7
commit cd22da8735
21 changed files with 848 additions and 126 deletions
+116
View File
@@ -994,6 +994,12 @@
}
break;
}
case 'companyResumable': {
if (typeof window.__renderCompanyResumable === 'function') {
window.__renderCompanyResumable(msg.value || {});
}
break;
}
case 'companyPipelineTemplateContent': {
const tpl = msg.value;
if (!tpl) { showToast('템플릿을 찾을 수 없습니다.', 'warn'); break; }
@@ -1034,6 +1040,7 @@
// Triggered by the Command Palette `Manage 1인 기업 Agents`.
document.getElementById('companyOverlay')?.classList.add('visible');
vscode.postMessage({ type: 'getCompanyAgents' });
vscode.postMessage({ type: 'getCompanyResumable' });
break;
}
case 'companyTurnUpdate': {
@@ -1717,6 +1724,7 @@
_companyStatusEl.textContent = '불러오는 중...';
vscode.postMessage({ type: 'getCompanyAgents' });
vscode.postMessage({ type: 'getCompanyPipelines' });
vscode.postMessage({ type: 'getCompanyResumable' });
};
}
for (const btn of _closeCompanyBtns) {
@@ -2368,6 +2376,114 @@
// expose for the message handler below
window.__renderCompanyPipelines = renderCompanyPipelines;
window.__closePipelineEditor = _closePipelineEditor;
// ──────────────────────────────────────────────────────────────────────
// 이어서 진행 가능 세션 렌더링.
//
// 백엔드가 보낸 items 배열을 카드 목록으로 그린다. 비어 있으면 섹션 자체를
// data-empty="true"로 숨겨 평소 시야에서 사라지게 만든다. 카드는 두 액션:
// - 이어서 진행 → resumeCompanyTurn 메시지
// - 버리기 → discardResumableSession (resume 파일을 'failed'로 마킹)
// ──────────────────────────────────────────────────────────────────────
const _formatRelative = (iso) => {
if (!iso) return '';
const d = new Date(iso); if (Number.isNaN(d.getTime())) return iso;
const diff = Date.now() - d.getTime();
const m = Math.round(diff / 60000);
if (m < 1) return '방금 전';
if (m < 60) return `${m}분 전`;
const h = Math.round(m / 60);
if (h < 24) return `${h}시간 전`;
const days = Math.round(h / 24);
return `${days}일 전`;
};
const _formatAbortReason = (reason) => {
if (!reason) return '도중에 멈춤';
const map = {
'signal-aborted': '시작 직전에 중단',
'aborted-after-plan': '계획 직후 중단',
'aborted-mid-dispatch': '실행 중에 중단',
'aborted-mid-pipeline': '단계 진행 중 중단',
'aborted-mid-approval': '승인 대기 중 중단',
'aborted-by-user-at-approval': '승인 단계에서 중단',
'aborted-before-report': '보고서 직전 중단',
};
return map[reason] || reason;
};
const renderCompanyResumable = (payload) => {
const section = document.getElementById('companyResumableSection');
const list = document.getElementById('companyResumableList');
if (!section || !list) return;
const items = (payload && Array.isArray(payload.items)) ? payload.items : [];
list.innerHTML = '';
if (items.length === 0) {
section.setAttribute('data-empty', 'true');
return;
}
section.setAttribute('data-empty', 'false');
for (const it of items) {
const li = document.createElement('li');
li.className = 'company-resumable-card';
li.dataset.timestamp = it.timestamp;
const head = document.createElement('div');
head.className = 'company-resumable-head';
const prompt = document.createElement('div');
prompt.className = 'company-resumable-prompt';
prompt.textContent = it.userPrompt || '(빈 요청)';
prompt.title = it.userPrompt || '';
const actions = document.createElement('div');
actions.className = 'company-resumable-actions';
const resumeBtn = document.createElement('button');
resumeBtn.className = 'primary company-resumable-resume';
resumeBtn.textContent = '이어서 진행';
resumeBtn.title = '이 작업을 멈췄던 다음 단계부터 같은 세션에 이어 기록합니다.';
resumeBtn.onclick = () => {
// 사용자에게 곧 시작될 거라는 시각 피드백.
resumeBtn.disabled = true;
resumeBtn.textContent = '재개 중…';
vscode.postMessage({ type: 'resumeCompanyTurn', timestamp: it.timestamp });
// overlay를 닫아 채팅 화면이 보이게 — 사용자가 진행 상황 즉시 확인.
document.getElementById('companyOverlay')?.classList.remove('visible');
};
const discardBtn = document.createElement('button');
discardBtn.className = 'company-resumable-discard';
discardBtn.textContent = '버리기';
discardBtn.title = '이 작업을 더 이상 이어가지 않습니다. 목록에서만 빠지고 기존 산출물 파일은 그대로 남습니다.';
discardBtn.onclick = () => {
if (!confirm('이 미완 작업을 목록에서 버릴까요? 이미 만들어진 산출물 파일은 사라지지 않습니다.')) return;
vscode.postMessage({ type: 'discardResumableSession', timestamp: it.timestamp });
};
actions.appendChild(resumeBtn);
actions.appendChild(discardBtn);
head.appendChild(prompt);
head.appendChild(actions);
li.appendChild(head);
const meta = document.createElement('div');
meta.className = 'company-resumable-meta';
const pipelineLabel = it.pipelineName
? `📋 ${escAttr(it.pipelineName)}`
: '🧭 대표 분배 모드';
const progress = (it.totalCount > 0)
? `${it.completedCount}/${it.totalCount} 단계 완료`
: '진행도 정보 없음';
const when = _formatRelative(it.lastUpdatedAt);
const why = it.status === 'aborted'
? `· ${_formatAbortReason(it.abortReason)}`
: (it.status === 'in-progress' ? '· 프로세스 중단 추정' : '');
meta.innerHTML = `<span>${pipelineLabel}</span><span>${escAttr(progress)}</span><span>${escAttr(when)} ${escAttr(why)}</span>`;
li.appendChild(meta);
list.appendChild(li);
}
};
window.__renderCompanyResumable = renderCompanyResumable;
// 템플릿 stamp 시 호출 — id/name 제안값 + stages를 카드 에디터에 미리 채움.
window.__openPipelineEditorWithTemplate = (tpl) => {
if (!tpl) return;