Release v2.2.1: Autonomous Task Resumption & Engine Resilience
This commit is contained in:
@@ -691,6 +691,54 @@
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 이어서 진행 섹션 — 빈 목록이면 통째로 숨김. 있을 때만 노출돼서
|
||||
평소 시야 부담이 없고, 사용자가 멈춘 작업을 즉시 식별 가능. */
|
||||
.company-resumable-section[data-empty="true"] { display: none; }
|
||||
.company-resumable-list {
|
||||
list-style: none;
|
||||
display: flex; flex-direction: column; gap: 8px;
|
||||
margin-top: 8px; padding: 0;
|
||||
}
|
||||
.company-resumable-card {
|
||||
border: 1px solid var(--warning);
|
||||
background: linear-gradient(180deg, rgba(210,153,34,0.08), transparent 70%);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
display: flex; flex-direction: column; gap: 6px;
|
||||
}
|
||||
.company-resumable-head {
|
||||
display: flex; gap: 10px; align-items: flex-start; justify-content: space-between;
|
||||
}
|
||||
.company-resumable-prompt {
|
||||
color: var(--text-bright); font-size: 12.5px; font-weight: 600;
|
||||
line-height: 1.4; flex: 1; min-width: 0;
|
||||
overflow: hidden; text-overflow: ellipsis;
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
||||
}
|
||||
.company-resumable-actions {
|
||||
display: flex; gap: 6px; flex-shrink: 0;
|
||||
}
|
||||
.company-resumable-resume {
|
||||
font-size: 11px; padding: 5px 11px;
|
||||
background: var(--accent); color: var(--bg);
|
||||
border: 0; border-radius: 6px; cursor: pointer; font-weight: 600;
|
||||
}
|
||||
.company-resumable-resume:hover { filter: brightness(1.1); }
|
||||
.company-resumable-resume[disabled] { opacity: 0.55; cursor: progress; }
|
||||
.company-resumable-discard {
|
||||
font-size: 11px; padding: 5px 11px;
|
||||
background: transparent; color: var(--text-dim);
|
||||
border: 1px solid var(--border); border-radius: 6px; cursor: pointer;
|
||||
}
|
||||
.company-resumable-discard:hover {
|
||||
color: var(--error); border-color: var(--error);
|
||||
}
|
||||
.company-resumable-meta {
|
||||
display: flex; flex-wrap: wrap; gap: 8px;
|
||||
font-size: 10px; color: var(--text-dim);
|
||||
}
|
||||
.company-resumable-meta span { white-space: nowrap; }
|
||||
.pipeline-summary-head {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
@@ -146,6 +146,19 @@
|
||||
<button class="icon-btn" id="closeCompanyOverlayBtn">✕</button>
|
||||
</div>
|
||||
|
||||
<!-- 이어서 진행할 수 있는 미완 작업. 빈 목록이면 JS가 섹션 자체를 숨겨서
|
||||
평소에는 시야에 들어오지 않는다. 각 카드는 [이어서 진행] / [버리기]
|
||||
두 버튼만 노출. -->
|
||||
<div id="companyResumableSection" class="map-section company-resumable-section" data-empty="true">
|
||||
<div class="map-section-head">
|
||||
<div>
|
||||
<div class="map-section-title">이어서 진행할 수 있는 작업</div>
|
||||
<div class="map-section-hint">중간에 멈춘 작업이 있을 때만 노출됩니다. 누른 시점부터 다음 단계가 같은 세션 폴더에 이어 기록됩니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="companyResumableList" class="map-list company-resumable-list"></ul>
|
||||
</div>
|
||||
|
||||
<div class="map-section">
|
||||
<div class="map-section-head">
|
||||
<div>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user