feat(growth): 주간 성장 사이클 자동화 + 텔레그램 양방향 HITL (v2.2.220)
P4 — Self-Evolving OS 폐루프 자동화: - growthCycleWatcher: 매주(기본 일 20:00 KST, 설정 가능) 자동으로 ① 골든셋 검색 평가(recall/MRR 주간 추이) ② 학습 큐 갱신(Need Engine) ③ 지식 노후 점검 ④ 성장 리포트 ⑤ 승인(approved)된 학습 큐 항목을 Research Agent 로 자동 실행(사이클당 최대 3건) ⑥ 요약 알림+텔레그램. 승인 자체는 여전히 사람 — Permission Based Learning 유지, 자동화되는 것은 '승인된 것의 실행'뿐. 결과물은 기존 수동 명령과 동일 위치 (.astra/eval/, .astra/growth/) — 완전 호환. 수동 트리거 명령 (growthCycle.runNow) 제공. 단계별 독립 try/catch. P5 — 텔레그램 양방향 HITL: - /meet confirm 코어를 출력 중립 processConfirmDecisions 로 추출 (웹뷰·텔레그램 공용) — 핸들러는 위임 호출로 슬림화. - 텔레그램 인바운드에 confirm/pending(보류) 분기 — 회사 밖에서 "confirm 1=ok 2=6/20 3=skip" 회신으로 보류 액션 등록 완결. - 데일리 브리핑에 보류 목록 + 회신 안내 포함 — 아침 브리핑에서 바로 확정하는 흐름 완성. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -28,8 +28,8 @@ import { buildMeetPrompt, buildMeetExtractPrompt, buildMeetReducePrompt, buildMe
|
||||
import { createCalendarEvent, createTask, readCalendarConfig } from '../calendar';
|
||||
import {
|
||||
transcriptHash, taskKey, loadRegisteredKeys, markRegistered,
|
||||
savePending, loadPending, clearPending, classifyAction,
|
||||
registerAction, buildNotes, parseConfirmArgs, renderPendingQuestion,
|
||||
savePending, loadPending, classifyAction,
|
||||
registerAction, buildNotes, renderPendingQuestion, processConfirmDecisions,
|
||||
loadGlossaryTerms, updateGlossary, extractGlossaryCandidates,
|
||||
type PendingItem, type PendingFile,
|
||||
} from './scheduling/meetRegistration';
|
||||
@@ -780,109 +780,13 @@ async function runMeet(arg: string, view: Webview | undefined, context?: vscode.
|
||||
* 같은 녹취 재실행 시 중복 등록되지 않는다.
|
||||
*/
|
||||
async function runMeetConfirm(arg: string, view: Webview | undefined, context?: vscode.ExtensionContext): Promise<void> {
|
||||
const pend = loadPending();
|
||||
if (!pend || !pend.items.length) {
|
||||
chunk(view, '\nℹ️ 등록 보류 중인 액션 아이템이 없습니다. (`/meet <녹취파일>` 실행 후 보류가 생기면 사용)\n');
|
||||
return;
|
||||
}
|
||||
if (!context) {
|
||||
chunk(view, '\n⚠️ 확장 컨텍스트를 사용할 수 없어 등록을 진행할 수 없습니다.\n');
|
||||
return;
|
||||
}
|
||||
if (!arg.trim()) {
|
||||
chunk(view, renderPendingQuestion(pend) + '\n');
|
||||
return;
|
||||
}
|
||||
const calCfg = readCalendarConfig(context);
|
||||
if (!calCfg.refreshToken) {
|
||||
chunk(view, '\n⚠️ Google OAuth(쓰기)가 연결되지 않아 등록할 수 없습니다. (Astra Settings → Google 섹션)\n');
|
||||
return;
|
||||
}
|
||||
const gCfg = vscode.workspace.getConfiguration('g1nation');
|
||||
const useTasks = gCfg.get<boolean>('meetUsesTasks', true);
|
||||
const useCalendar = gCfg.get<boolean>('meetUsesCalendar', true);
|
||||
|
||||
const { decisions, errors } = parseConfirmArgs(arg, new Date().getFullYear());
|
||||
for (const e of errors) chunk(view, ` ⚠️ ${e}\n`);
|
||||
if (!decisions.length) {
|
||||
chunk(view, '\n해석 가능한 답변이 없습니다. 예: `/meet confirm 1=6/20 2=ok 3=skip`\n');
|
||||
return;
|
||||
}
|
||||
|
||||
let registered = 0, skipped = 0, failed = 0;
|
||||
const doneIdx = new Set<number>();
|
||||
const newKeys: string[] = [];
|
||||
|
||||
for (const d of decisions) {
|
||||
const item = pend.items.find(i => i.idx === d.idx);
|
||||
if (!item) { chunk(view, ` ⚠️ ${d.idx}번 항목이 보류 목록에 없습니다.\n`); continue; }
|
||||
|
||||
if (d.action === 'skip') {
|
||||
skipped++;
|
||||
doneIdx.add(item.idx);
|
||||
chunk(view, ` · ⏭️ ${item.idx}. ${item.work} — 등록 안 함\n`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const isConditional = item.kind === 'conditional';
|
||||
let title: string;
|
||||
let date: string | undefined;
|
||||
const extra: string[] = [];
|
||||
|
||||
if (isConditional) {
|
||||
extra.push(`■ 선행 조건: ${item.condition}`);
|
||||
if (d.action === 'ok') {
|
||||
// 날짜 없는 Tasks 등록 — 조건 충족 시 사용자가 날짜를 부여.
|
||||
title = `[조건부] ${item.work}`;
|
||||
date = undefined;
|
||||
extra.push('· 선행 조건 충족 후 진행 — 날짜 없는 task 로 등록됨 (충족 시 날짜 부여)');
|
||||
if (!useTasks) {
|
||||
failed++;
|
||||
chunk(view, ` · ⚠️ ${item.idx}. ${item.work} — 날짜 없는 등록은 Tasks 가 필요합니다 (meetUsesTasks 꺼짐). 확인일 날짜를 지정해주세요: \`${item.idx}=날짜\`\n`);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
title = `[조건부 확인] ${item.work}`;
|
||||
date = d.date!;
|
||||
extra.push(`· ${date} 는 선행 조건 충족 여부를 점검하는 확인일입니다.`);
|
||||
}
|
||||
} else {
|
||||
title = item.work;
|
||||
date = d.action === 'ok' ? item.suggestedDate : d.date!;
|
||||
}
|
||||
|
||||
const notes = buildNotes({
|
||||
detail: item.detail, meetTitle: pend.meetTitle, owner: item.owner,
|
||||
dueRaw: item.due, dateLabel: date || '(날짜 없음 — 조건부)', extra,
|
||||
});
|
||||
const r = await registerAction(context, {
|
||||
title, date, notes,
|
||||
useTasks, useCalendar: isConditional && !date ? false : useCalendar,
|
||||
});
|
||||
if (r.failures.length === 0) {
|
||||
registered++;
|
||||
doneIdx.add(item.idx);
|
||||
newKeys.push(taskKey(item.work));
|
||||
chunk(view, ` · ✅ ${date || '(날짜 없음)'} — ${title} (${r.successes.join(' + ')})\n`);
|
||||
} else {
|
||||
failed++;
|
||||
if (r.successes.length) { doneIdx.add(item.idx); newKeys.push(taskKey(item.work)); }
|
||||
chunk(view, ` · ${date || '(날짜 없음)'} — ${title}${r.successes.length ? ` (✅ ${r.successes.join(' + ')})` : ''}\n`);
|
||||
for (const f of r.failures) chunk(view, ` ⚠️ ${f}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
if (newKeys.length) markRegistered(pend.transcriptHash, pend.meetTitle, newKeys);
|
||||
|
||||
// 처리된 항목 제거 — 남은 보류는 유지 후 안내.
|
||||
const remaining = pend.items.filter(i => !doneIdx.has(i.idx));
|
||||
if (remaining.length === 0) {
|
||||
clearPending();
|
||||
chunk(view, `\n✅ 보류 항목 처리 완료 — 등록 ${registered}건 · 건너뜀 ${skipped}건${failed ? ` · 실패 ${failed}건` : ''}\n`);
|
||||
} else {
|
||||
savePending({ ...pend, items: remaining });
|
||||
chunk(view, `\n등록 ${registered}건 · 건너뜀 ${skipped}건${failed ? ` · 실패 ${failed}건` : ''} — 아직 ${remaining.length}건이 보류 중입니다 (\`/meet pending\` 으로 확인)\n`);
|
||||
}
|
||||
// 코어는 출력 중립(processConfirmDecisions) — 텔레그램 인바운드와 공용 (P5 HITL).
|
||||
const { lines } = await processConfirmDecisions(context, arg);
|
||||
chunk(view, '\n' + lines.map(l => ` ${l}`).join('\n') + '\n');
|
||||
}
|
||||
|
||||
// ─── 등록 ─────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user