47b3b9f93a
- v2.2.299 아키텍처 수렴: coreChat 통일(엔진 휴리스틱 3벌 제거), 기업 모드 검색 오케스트레이터 승격, lib/execUtil(실행 래퍼 6곳·Python 탐지 3벌 단일화), lib/kstSchedule(워처 4개 nowInKst 통합), estimateTokens 통합, 설정 접근 규칙 명문화 - v2.2.300 채팅 화면 정리: LiveReasoningFilter(스트리밍 중 <think>/Harmony 추론 토큰 단위 차단), 확신도·검토요청 footer 기본 숨김(계산·Reflection 은 유지) - v2.2.301 문맥·의도 이해: [답변 전 이해 원칙] 상시 주입, 워크플로우 의도 브리핑, Report QA 루프(규칙 레지스트리+실측치+회귀 게이트, 블로그_v3 개념 이식) - v2.2.302 /benchmark 비즈니스 렌즈(가격·수익·운영)+빌드 프롬프트 모드+QA 연계 - v2.2.303 handoff 모드(측정치 무손실 인수인계 문서)+/claude(Claude Code 터미널 위임) - v2.2.304 이식성: 지식 경로 두뇌-상대 규약(pickWikiDir 상대 해석), 이사 체크리스트 - v2.2.305 Claude 구독 엔진: claude: 프로바이더(CLI 위임, 모델 드롭다운 자동 노출, coreChat 지원 — 워크플로우·QA도 구독 모델 가능) - v2.2.306 Tone Guard: AI 상투어 금지 레지스트리(상담사 화법 실사례 8종+대조 예시) - v2.2.307 /benchmark 레이아웃 골격(sectionRoles 결정론 분석, 롤링 배너 즉답), 파트별 실패 격리, 합성 타임아웃 120→300초 검증: tsc 무오류 + jest 888 통과 + esbuild 정상 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
193 lines
10 KiB
TypeScript
193 lines
10 KiB
TypeScript
import * as vscode from 'vscode';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { getConfig } from '../config';
|
|
import { execShell } from '../lib/execUtil';
|
|
import { logInfo, logWarn, logError, getActiveBrainProfile } from '../utils';
|
|
import { getBridgeBaseUrl } from '../features/datacollect/bridgeClient';
|
|
|
|
/**
|
|
* HealthCheckMonitor: Periodically monitors the environment
|
|
* (Ollama, Disk, API) to ensure the agent stays functional.
|
|
*
|
|
* v2.2.245: 머신 로컬 전제 조건 4종 추가 — Bridge 응답·두뇌 볼륨 마운트·
|
|
* git push 자격증명·Antigravity 확장 버전 정합. 한 세션에서 이 4가지가 전부
|
|
* 사고로 터진 적이 있는데 (URL 분석 실패 / 인벤토리 미생성 / 동기화 실패 /
|
|
* 구버전 실행), 전부 사후 디버깅으로만 발견됐다. 이제 readyBar ⚠️ 로 사전 가시화.
|
|
*
|
|
* Properly tracks the interval timer for cleanup on deactivation.
|
|
*/
|
|
export class HealthCheckMonitor {
|
|
private static intervalHandle: ReturnType<typeof setInterval> | null = null;
|
|
/** 마지막 검사 결과 — readyBar payload 가 읽어간다 (재검사 비용 없이). */
|
|
private static _lastReports: string[] = [];
|
|
/** 직전 토스트 내용 — 같은 경고를 10분마다 반복 토스트하지 않기 위한 dedupe. */
|
|
private static _lastToastKey = '';
|
|
/** 인터벌 틱 카운터 — 느린 검사(git push·레지스트리)를 매 틱이 아니라 N틱마다 돌리기 위함. */
|
|
private static _tick = 0;
|
|
/** 느린 검사(git 자격증명·Antigravity 버전)의 최근 결과 캐시 — 빠른 틱에도 경고 표시가 유지되게. */
|
|
private static _slowReports: string[] = [];
|
|
|
|
public static get lastReports(): string[] {
|
|
return this._lastReports.slice();
|
|
}
|
|
|
|
/**
|
|
* @param includeSlowChecks git push --dry-run(원격 네트워크)·Antigravity 레지스트리 읽기 같은
|
|
* 비싼 검사 포함 여부. 활성화 시·수동 호출은 true(기본), 주기 인터벌은 3틱(30분)마다만 true.
|
|
* false 틱에서는 직전 느린 검사 결과(_slowReports)를 그대로 합쳐 경고 표시가 깜빡이지 않게 한다.
|
|
*/
|
|
public static async runAllChecks(includeSlowChecks: boolean = true): Promise<{ ok: boolean; reports: string[] }> {
|
|
const reports: string[] = [];
|
|
const config = getConfig();
|
|
|
|
// 1. AI Engine Connectivity Check
|
|
try {
|
|
const res = await fetch(`${config.ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(3000) });
|
|
if (res.ok) {
|
|
logInfo('Health Check: AI engine connectivity OK.');
|
|
} else {
|
|
reports.push('AI Server is reachable but returned an error.');
|
|
}
|
|
} catch {
|
|
reports.push('AI Server is NOT reachable. Please check if it is running.');
|
|
}
|
|
|
|
// 2. Workspace Validation
|
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
|
if (!workspaceFolders || workspaceFolders.length === 0) {
|
|
reports.push('No workspace folder open. Agent capabilities will be limited.');
|
|
}
|
|
|
|
// 3. Simple Disk/Permissions Check
|
|
// [발열 개선] 예전엔 워크스페이스에 파일을 실제로 쓰고 지웠는데, 워크스페이스 파일 변경은
|
|
// VS Code 본체와 모든 확장의 파일 워처를 주기마다 연쇄로 깨운다. fs.access(W_OK)는
|
|
// 파일을 만들지 않고 같은 질문("여기 쓸 수 있나")에 답하므로 워처 연쇄가 없다.
|
|
try {
|
|
if (workspaceFolders && workspaceFolders[0].uri.scheme === 'file') {
|
|
await fs.promises.access(workspaceFolders[0].uri.fsPath, fs.constants.W_OK);
|
|
logInfo('Health Check: Write permissions OK.');
|
|
}
|
|
} catch {
|
|
reports.push('Write permissions denied in the current workspace.');
|
|
}
|
|
|
|
// 4. Datacollect Bridge 응답 — 어떤 HTTP 응답이든(404 포함) 살아 있는 것.
|
|
// 네트워크 오류만 다운으로 판정. Bridge 다운이어도 URL 분석은 직접
|
|
// fetch 폴백으로 동작하므로 경고는 영향 범위를 정확히 알린다.
|
|
try {
|
|
const bridgeUrl = getBridgeBaseUrl();
|
|
if (bridgeUrl) {
|
|
try {
|
|
await fetch(bridgeUrl, { signal: AbortSignal.timeout(3000) });
|
|
} catch {
|
|
reports.push(`Datacollect Bridge(${bridgeUrl})가 응답하지 않습니다 — /wikify·/benchmark 와 고품질 URL 추출이 비활성 (URL 분석 자체는 직접 fetch 폴백으로 동작).`);
|
|
}
|
|
}
|
|
} catch { /* config 읽기 실패 등 — 검사 자체를 조용히 skip */ }
|
|
|
|
// 5. 두뇌 볼륨/경로 — 외장 볼륨이 늦게 마운트되면 검색·레슨·인벤토리가
|
|
// 조용히 비활성화되던 사고의 사전 감지.
|
|
let brain: ReturnType<typeof getActiveBrainProfile> | undefined;
|
|
try {
|
|
brain = getActiveBrainProfile();
|
|
if (brain?.localBrainPath && !fs.existsSync(brain.localBrainPath)) {
|
|
reports.push(`두뇌 폴더가 없습니다 (외장 볼륨 미마운트?): ${brain.localBrainPath} — 검색·레슨·인벤토리가 비활성화됩니다.`);
|
|
}
|
|
} catch { /* profile 읽기 실패 — skip */ }
|
|
|
|
// ── 느린 검사 (6·7): 원격 네트워크·프로세스 생성·레지스트리 파싱 — 매 틱 돌리기엔 비싸다.
|
|
// includeSlowChecks 틱에서만 실행하고, 그 외 틱은 직전 결과를 그대로 합친다.
|
|
if (includeSlowChecks) {
|
|
const slowReports: string[] = [];
|
|
|
|
// 6. git push 자격증명 — 원격 동기화를 *설정한* 두뇌만 검사 (secondBrainRepo
|
|
// 비어 있으면 두뇌 동기화가 로컬 새로고침 모드라 자격증명 불필요).
|
|
// [발열 개선] execSync(최대 5초 확장 호스트 블로킹) → 비동기 exec.
|
|
try {
|
|
if (brain?.secondBrainRepo?.trim() && brain.localBrainPath && fs.existsSync(brain.localBrainPath)) {
|
|
try {
|
|
await execShell('git push --dry-run', {
|
|
cwd: brain.localBrainPath,
|
|
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
timeoutMs: 5000,
|
|
});
|
|
} catch (e: any) {
|
|
const msg = String(e?.stderr || e?.message || '');
|
|
if (/could not read Username|Authentication failed|terminal prompts disabled/i.test(msg)) {
|
|
slowReports.push(`두뇌 원격 push 자격증명이 없습니다 — 터미널에서 "cd ${brain.localBrainPath} && git push" 1회 실행으로 키체인에 저장하세요.`);
|
|
}
|
|
// 그 외(오프라인·upstream 없음 등)는 소음 방지 — 보고하지 않음.
|
|
}
|
|
}
|
|
} catch { /* skip */ }
|
|
|
|
// 7. Antigravity 확장 버전 정합 — VS Code 와 Antigravity 양쪽에 설치된
|
|
// 경우 버전이 갈리면 "고쳤는데 안 고쳐짐" 혼란의 단골 원인 (실사례:
|
|
// 2.2.19 vs 2.2.232 공존). Antigravity 미사용 머신에서는 자동 skip.
|
|
try {
|
|
const agExtRegistry = path.join(os.homedir(), '.antigravity', 'extensions', 'extensions.json');
|
|
if (fs.existsSync(agExtRegistry)) {
|
|
const arr = JSON.parse(fs.readFileSync(agExtRegistry, 'utf8'));
|
|
const mine = Array.isArray(arr)
|
|
? arr.find((e: any) => e?.identifier?.id === 'g1nation.astra')
|
|
: null;
|
|
const myVersion = vscode.extensions.getExtension('g1nation.astra')?.packageJSON?.version;
|
|
if (mine?.version && myVersion && mine.version !== myVersion) {
|
|
slowReports.push(`Antigravity 에 다른 버전의 Astra(${mine.version})가 설치되어 있습니다 — 현재 실행 중 ${myVersion}. Antigravity 쪽은 수동 업데이트가 필요합니다.`);
|
|
}
|
|
}
|
|
} catch { /* registry 파싱 실패 — skip */ }
|
|
|
|
this._slowReports = slowReports;
|
|
}
|
|
reports.push(...this._slowReports);
|
|
|
|
this._lastReports = reports;
|
|
|
|
if (reports.length > 0) {
|
|
logWarn(`Health Check Warnings: ${reports.join(' | ')}`);
|
|
// 동일 경고 반복 토스트 방지 — 내용이 바뀐 경우에만 1회 토스트.
|
|
const toastKey = reports.join('|');
|
|
if (toastKey !== this._lastToastKey) {
|
|
this._lastToastKey = toastKey;
|
|
vscode.window.showWarningMessage(`Astra Health Warning: ${reports[0]}${reports.length > 1 ? ` (외 ${reports.length - 1}건 — 준비 상태 바 참조)` : ''}`);
|
|
}
|
|
} else {
|
|
this._lastToastKey = '';
|
|
}
|
|
|
|
return {
|
|
ok: reports.length === 0,
|
|
reports
|
|
};
|
|
}
|
|
|
|
public static startInterval(ms: number = 300000) {
|
|
// Prevent duplicate intervals
|
|
if (this.intervalHandle !== null) {
|
|
clearInterval(this.intervalHandle);
|
|
}
|
|
// 빠른 검사(서버·권한·브리지·두뇌 경로)는 매 틱, 느린 검사(git 원격·레지스트리)는 3틱마다.
|
|
// (기본 10분 인터벌 기준: 빠른 검사 10분, 느린 검사 30분 주기)
|
|
this._tick = 0;
|
|
this.intervalHandle = setInterval(() => {
|
|
this._tick++;
|
|
this.runAllChecks(this._tick % 3 === 0);
|
|
}, ms);
|
|
}
|
|
|
|
/**
|
|
* Stops periodic health checks and releases resources.
|
|
* Should be called from extension.deactivate().
|
|
*/
|
|
public static dispose(): void {
|
|
if (this.intervalHandle !== null) {
|
|
clearInterval(this.intervalHandle);
|
|
this.intervalHandle = null;
|
|
logInfo('Health Check: Interval stopped.');
|
|
}
|
|
}
|
|
}
|