diff --git a/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json b/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json index dfb63e9..66d8db8 100644 --- a/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json +++ b/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json @@ -1,5 +1,5 @@ { "result": "Final report with inconsistencies. This should be long enough to pass validation.", - "createdAt": 1779250764072, + "createdAt": 1779280514827, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/cache/65775be352df43297b63c7af59c9f4f39d2bc368f77456c37b5eef9a94a66b5c.json b/.astra/tests/stress/.astra/cache/65775be352df43297b63c7af59c9f4f39d2bc368f77456c37b5eef9a94a66b5c.json index 4494ade..a0b6c7f 100644 --- a/.astra/tests/stress/.astra/cache/65775be352df43297b63c7af59c9f4f39d2bc368f77456c37b5eef9a94a66b5c.json +++ b/.astra/tests/stress/.astra/cache/65775be352df43297b63c7af59c9f4f39d2bc368f77456c37b5eef9a94a66b5c.json @@ -1,5 +1,5 @@ { "result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.", - "createdAt": 1779250764070, + "createdAt": 1779280514827, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/cache/6894d26c5b0a55d25d756a473225c7a44d7661af673b24e3f49551a7a2e50280.json b/.astra/tests/stress/.astra/cache/6894d26c5b0a55d25d756a473225c7a44d7661af673b24e3f49551a7a2e50280.json index e9eacb7..b3f171e 100644 --- a/.astra/tests/stress/.astra/cache/6894d26c5b0a55d25d756a473225c7a44d7661af673b24e3f49551a7a2e50280.json +++ b/.astra/tests/stress/.astra/cache/6894d26c5b0a55d25d756a473225c7a44d7661af673b24e3f49551a7a2e50280.json @@ -1,5 +1,5 @@ { "result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.", - "createdAt": 1779250764068, + "createdAt": 1779280514826, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/cache/88cb61499f88ed38165b64bd3e8adc543795e4b427b64540a49c9ab27c7fe213.json b/.astra/tests/stress/.astra/cache/88cb61499f88ed38165b64bd3e8adc543795e4b427b64540a49c9ab27c7fe213.json index f491b4b..09247dc 100644 --- a/.astra/tests/stress/.astra/cache/88cb61499f88ed38165b64bd3e8adc543795e4b427b64540a49c9ab27c7fe213.json +++ b/.astra/tests/stress/.astra/cache/88cb61499f88ed38165b64bd3e8adc543795e4b427b64540a49c9ab27c7fe213.json @@ -1,5 +1,5 @@ { - "result": "---\nid: stress_conflict_1779250764052\ndate: 2026-05-20T04:19:24.073Z\ntype: knowledge_artifact\nstandard: P-Reinforce v3.0\ntags: [automated, connect_ai, brain_sync]\n---\n\n## 📌 Brief Summary\nFinal report with inconsistencies. This should be long enough to pass validation.\n\nFinal report with inconsistencies. This should be long enough to pass validation.\n\n---\n## 💡 Astra의 선제적 제안 (Proactive Next Actions)\nFinal report with inconsistencies. This should be long enough to pass validation.\n---\n## 🛡️ Reliability & Audit Summary\n> [!NOTE]\n> 이 문서는 ConnectAI의 **Intelligent Resilience** 엔진에 의해 검증 및 정제되었습니다.\n\n| Metric | Value | Status |\n| :--- | :--- | :--- |\n| **Conflict Risk** | `60/100` | ⚠️ Medium |\n| **Fallbacks Used** | `0` | ✅ None |\n| **Auto Retries** | `0` | ✅ Stable |\n| **Deduplication** | `0` | Standard |\n| **Processing Time** | `0.0s` | ✅ Fast |\n\n### 🔍 Decision Audit Trail\n- **[PLANNER]** 전략 수립 중... (14ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (3ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (2ms)\n", - "createdAt": 1779250764073, + "result": "---\nid: stress_conflict_1779280514813\ndate: 2026-05-20T12:35:14.828Z\ntype: knowledge_artifact\nstandard: P-Reinforce v3.0\ntags: [automated, connect_ai, brain_sync]\n---\n\n## 📌 Brief Summary\nFinal report with inconsistencies. This should be long enough to pass validation.\n\nFinal report with inconsistencies. This should be long enough to pass validation.\n\n---\n## 💡 Astra의 선제적 제안 (Proactive Next Actions)\nFinal report with inconsistencies. This should be long enough to pass validation.\n---\n## 🛡️ Reliability & Audit Summary\n> [!NOTE]\n> 이 문서는 ConnectAI의 **Intelligent Resilience** 엔진에 의해 검증 및 정제되었습니다.\n\n| Metric | Value | Status |\n| :--- | :--- | :--- |\n| **Conflict Risk** | `60/100` | ⚠️ Medium |\n| **Fallbacks Used** | `0` | ✅ None |\n| **Auto Retries** | `0` | ✅ Stable |\n| **Deduplication** | `0` | Standard |\n| **Processing Time** | `0.0s` | ✅ Fast |\n\n### 🔍 Decision Audit Trail\n- **[PLANNER]** 전략 수립 중... (12ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (1ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (1ms)\n", + "createdAt": 1779280514828, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/missions/stress_conflict_1779250764052.json b/.astra/tests/stress/.astra/missions/stress_conflict_1779280514813.json similarity index 78% rename from .astra/tests/stress/.astra/missions/stress_conflict_1779250764052.json rename to .astra/tests/stress/.astra/missions/stress_conflict_1779280514813.json index 2dd2896..9f59b71 100644 --- a/.astra/tests/stress/.astra/missions/stress_conflict_1779250764052.json +++ b/.astra/tests/stress/.astra/missions/stress_conflict_1779280514813.json @@ -1,8 +1,8 @@ { - "missionId": "stress_conflict_1779250764052", + "missionId": "stress_conflict_1779280514813", "status": "completed", - "startTime": "2026-05-20T04:19:24.052Z", - "totalElapsedMs": 22, + "startTime": "2026-05-20T12:35:14.813Z", + "totalElapsedMs": 15, "results": { "planner": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.", "researcher": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.", @@ -16,30 +16,30 @@ { "from": "idle", "to": "planner", - "durationMs": 14, + "durationMs": 12, "message": "전략 수립 중...", - "ts": "2026-05-20T04:19:24.066Z" + "ts": "2026-05-20T12:35:14.825Z" }, { "from": "planner", "to": "researcher", - "durationMs": 3, + "durationMs": 1, "message": "핵심 정보 수집 및 분석 중...", - "ts": "2026-05-20T04:19:24.069Z" + "ts": "2026-05-20T12:35:14.826Z" }, { "from": "researcher", "to": "writer", - "durationMs": 2, + "durationMs": 1, "message": "최종 리포트 작성 및 편집 중...", - "ts": "2026-05-20T04:19:24.071Z" + "ts": "2026-05-20T12:35:14.827Z" }, { "from": "writer", "to": "completed", - "durationMs": 3, + "durationMs": 1, "message": "미션 완료", - "ts": "2026-05-20T04:19:24.074Z" + "ts": "2026-05-20T12:35:14.828Z" } ], "resilienceMetrics": { diff --git a/PATCHNOTES.md b/PATCHNOTES.md index 806a281..1a4682e 100644 --- a/PATCHNOTES.md +++ b/PATCHNOTES.md @@ -1,5 +1,25 @@ # Astra Patch Notes +## v2.2.52 (2026-05-20) +### 📦 재패키징 (v2.2.51 동일 내용) +- 기능 변경 없음 — v2.2.51 작업 트리를 버전만 올려 재패키징. 버전 정합성 정리를 위해 `package-lock.json` 버전도 함께 2.2.52로 동기화. +- **신규 패키징:** `astra-2.2.52.vsix`. + +--- + + + +## v2.2.51 (2026-05-20) +### 🐛 채팅 기록 목록 누락 수정 + Python/tsc 검증 크로스플랫폼화 +- **기록(Chat History) 목록에 최근 대화가 안 뜨던 문제 수정.** 답변을 다 받은 뒤 후처리 단계(아키텍처 감지·Self-Reflector·Chronicle 자동기록 등)에서 예외가 한 번이라도 나면 `_saveCurrentSession()`이 통째로 건너뛰어져 화면엔 답변이 보여도 기록 목록엔 안 들어가던 회귀. 일반 채팅·회사 모드 가벼운 대화 경로를 `try/finally`로 감싸 저장을 **항상** 보장하고, `_saveCurrentSession()` 자체도 절대 throw하지 않게 방어 처리. +- **1인 기업 모드 업무 턴도 기록 목록에 기록.** `_runCompanyTurn`은 dispatcher로 돌아 `_agent` 히스토리를 채우지 않아 기록 목록에 영영 안 남던 문제 — 완료된 업무 턴(`report-done`)을 `요청/보고서` 한 쌍으로 독립 항목 저장(`_saveCompanyTurnSession`). 이제 어느 모드든 최근 대화가 빠짐없이 기록됨. +- **Self-Reflector 실행 검증 크로스플랫폼화.** `.py` 검증을 `python`→`python3`(Windows는 `python` 우선)로 자동 탐지, `.ts` 검증을 `npx tsc` 대신 로컬 `node_modules/typescript/bin/tsc`를 `node`로 직접 호출 — macOS 12.3+/Windows에서 검증이 헛돌던 문제 해결. +- **신규 패키징:** `astra-2.2.51.vsix`. + +--- + + + ## v2.2.48 (2026-05-20) ### 🧹 출력 품질 — 내부 체크 로그 차단 + 한영 토큰 깨짐 정제 - **`[Self-Reflector Check]` 내부 검증 로그 노출 차단.** Self-Reflector Phase A를 기본 비활성화(`g1nation.selfReflector.enabled` 기본값 `true`→`false`). 답변 끝에 `Consistency/Completeness/Accuracy` 내부 체크 블록이 더 이상 붙지 않는다 — 일반 채팅·회사 모드 모두 적용. 기능 자체는 남아 설정에서 켤 수 있음. diff --git a/package-lock.json b/package-lock.json index 520d71a..471b4ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "astra", - "version": "2.1.2", + "version": "2.2.52", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "astra", - "version": "2.1.2", + "version": "2.2.52", "license": "MIT", "dependencies": { "@lmstudio/sdk": "^1.5.0", diff --git a/package.json b/package.json index c6de19c..54b5cc7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "astra", "displayName": "Astra", "description": "The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.", - "version": "2.2.48", + "version": "2.2.52", "publisher": "g1nation", "license": "MIT", "icon": "assets/icon.png", @@ -408,7 +408,9 @@ "g1nation.telegram.allowedChatIds": { "type": "array", "default": [], - "items": { "type": "number" }, + "items": { + "type": "number" + }, "description": "Optional allowlist of Telegram chat IDs that may message the bot. When empty, every chat that messages the bot is accepted (use with caution)." }, "g1nation.telegram.defaultAgent": { @@ -419,7 +421,9 @@ "g1nation.telegram.agentByChatId": { "type": "object", "default": {}, - "additionalProperties": { "type": "string" }, + "additionalProperties": { + "type": "string" + }, "description": "Per-chat override of the Telegram agent. Keys are stringified chat IDs, values are agent names from the knowledge map. Overrides telegram.defaultAgent for the listed chats." }, "g1nation.telegram.contextChunks": { @@ -490,7 +494,11 @@ }, "g1nation.company.intentAlignmentMode": { "type": "string", - "enum": ["off", "smart", "strict"], + "enum": [ + "off", + "smart", + "strict" + ], "default": "smart", "description": "Intent Alignment — turn user prompts into an explicit Requirement Contract (C-G-C-F-Q) before dispatching a pipeline. 'off' = legacy, pipeline runs immediately. 'smart' (default) = run when confidence is high, else show a confirmation card; ask up to N rounds of clarifying questions if information is missing. 'strict' = always show the contract card and require user confirmation, regardless of confidence. Goal: stop agents from silently guessing at the user's mental model." }, diff --git a/src/features/selfReflector/selfReflectorExecution.ts b/src/features/selfReflector/selfReflectorExecution.ts index 15d1494..156a90f 100644 --- a/src/features/selfReflector/selfReflectorExecution.ts +++ b/src/features/selfReflector/selfReflectorExecution.ts @@ -8,7 +8,7 @@ * 1. action-tag executor가 반환한 report를 받아 `✅ Created: ` / * `✅ Edited: ` 항목에서 경로를 추출 * 2. 파일 확장자별 toolchain 선택: - * .py → `python -m py_compile ` + * .py → `python3 -m py_compile ` (`python` on Windows) * .js / .mjs / .cjs → `node --check ` * .ts / .tsx → 프로젝트 단위 `tsc --noEmit` (단일 파일 체크는 의존성 때문에 실패율 높음) * .json → `JSON.parse` (node) @@ -21,7 +21,7 @@ * - 워크스페이스 외부 경로 무시 * - 사용자가 `executionVerification=false`면 통째로 skip — 호출자가 가드 */ -import { spawn } from 'child_process'; +import { spawn, spawnSync } from 'child_process'; import * as path from 'path'; import * as fs from 'fs'; import { logError, logInfo } from '../../utils'; @@ -76,11 +76,40 @@ function _firstNonEmptyLine(s: string): string { return (s || '').split(/\r?\n/).map((x) => x.trim()).find((x) => x.length > 0) ?? ''; } +/** + * Resolve the Python executable name once and cache it. + * + * macOS 12.3+ removed the bare `/usr/bin/python`; it ships `python3` only. + * Windows installers, conversely, expose `python` (and often no `python3`). + * So probe the platform-preferred name first, fall back to the other. + * + * Returns the platform-preferred default when neither is found so the caller + * still produces the usual "미설치" warning via the spawn-failure path. + */ +let _pythonCmdCache: string | undefined; +function _resolvePythonCmd(): string { + if (_pythonCmdCache !== undefined) return _pythonCmdCache; + const candidates = process.platform === 'win32' + ? ['python', 'python3'] + : ['python3', 'python']; + for (const cmd of candidates) { + try { + const r = spawnSync(cmd, ['--version'], { stdio: 'ignore', windowsHide: true }); + if (!r.error && (r.status === 0 || r.status === null)) { + _pythonCmdCache = cmd; + return cmd; + } + } catch { /* try next candidate */ } + } + _pythonCmdCache = candidates[0]; + return _pythonCmdCache; +} + /** 확장자별 검사 명령 결정. 지원 안 하는 확장자면 null 반환 (skip). */ function _pickTool(absPath: string, projectRoot: string): { cmd: string; args: string[]; cwd: string; label: string } | null { const ext = path.extname(absPath).toLowerCase(); if (ext === '.py') { - return { cmd: 'python', args: ['-m', 'py_compile', absPath], cwd: projectRoot, label: 'py_compile' }; + return { cmd: _resolvePythonCmd(), args: ['-m', 'py_compile', absPath], cwd: projectRoot, label: 'py_compile' }; } if (ext === '.js' || ext === '.mjs' || ext === '.cjs') { return { cmd: 'node', args: ['--check', absPath], cwd: projectRoot, label: 'node --check' }; @@ -99,8 +128,14 @@ function _pickTool(absPath: string, projectRoot: string): { cmd: string; args: s // 비용은 더 크지만 실제 사용자 환경에서 의미 있는 결과를 낸다. const tsconfig = path.join(projectRoot, 'tsconfig.json'); if (!fs.existsSync(tsconfig)) return null; + // `npx` is a `.cmd` shim on Windows and won't spawn under shell:false, + // so invoke the TypeScript compiler's plain-JS bin script via `node` + // directly — identical behavior on macOS, Linux and Windows. If the + // project has no local typescript install there is nothing to run. + const tscBin = path.join(projectRoot, 'node_modules', 'typescript', 'bin', 'tsc'); + if (!fs.existsSync(tscBin)) return null; return { - cmd: 'npx', args: ['--no-install', 'tsc', '--noEmit', '-p', tsconfig], + cmd: 'node', args: [tscBin, '--noEmit', '-p', tsconfig], cwd: projectRoot, label: 'tsc --noEmit', }; } diff --git a/src/sidebar/chatHandlers.ts b/src/sidebar/chatHandlers.ts index f543da7..482050b 100644 --- a/src/sidebar/chatHandlers.ts +++ b/src/sidebar/chatHandlers.ts @@ -191,9 +191,17 @@ export async function handleChatMessage(provider: SidebarChatProvider, data: any } return true; } - await provider._handlePrompt(data); - await provider._autoWriteChronicleAfterPrompt(); - await provider._saveCurrentSession(); + try { + await provider._handlePrompt(data); + await provider._autoWriteChronicleAfterPrompt(); + } finally { + // Persist the session even if _handlePrompt or the chronicle + // auto-write throws *after* the answer already streamed — + // otherwise the reply shows in the UI but never lands in the + // 기록(Chat History) list. This is the regression that made + // recent conversations stop appearing. + await provider._saveCurrentSession(); + } return true; case 'activity': provider._lmStudio?.activity.bump(); diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 3b09202..5808151 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -931,6 +931,17 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn } async _saveCurrentSession() { + try { + await this._saveCurrentSessionInner(); + } catch (err: any) { + // Never let a persistence failure escape — callers run this from a + // `finally` block, so a throw here would mask the original error + // and (worse) is itself the thing that drops chats from 기록. + logError('Failed to save current chat session.', { error: err?.message ?? String(err) }); + } + } + + private async _saveCurrentSessionInner() { const history = this._agent.getHistory(); if (history.length === 0) return; @@ -2358,9 +2369,14 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn type: 'companyIntentDecision', value: { intent, reason, label }, }); - await this._handlePrompt(originalData); - await this._autoWriteChronicleAfterPrompt(); - await this._saveCurrentSession(); + try { + await this._handlePrompt(originalData); + await this._autoWriteChronicleAfterPrompt(); + } finally { + // Same guarantee as the normal chat path — a throw after the + // answer streamed must not skip the 기록 save. + await this._saveCurrentSession(); + } } /** @@ -2561,12 +2577,18 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn // 잡아낸다 — turn이 중간 abort되면 plan만 남고 reportTail은 비어 // 있게 되는데, 그 상태로도 followup 매칭에는 충분히 도움된다. let stagingBrief = ''; + // Full final report of this turn — captured so the company turn can be + // recorded in the 기록(Chat History) list, same as a normal chat. The + // dispatcher runs outside AgentExecutor, so `_agent` history stays + // empty and `_saveCurrentSession()` would save nothing for it. + let finalReport = ''; const emit = (event: CompanyTurnEvent) => { this._view?.webview.postMessage({ type: 'companyTurnUpdate', value: event }); if (event.phase === 'plan-ready') { stagingBrief = event.plan?.brief || ''; } else if (event.phase === 'report-done') { - const tail = (event.report || '').trim().slice(-600); + finalReport = (event.report || '').trim(); + const tail = finalReport.slice(-600); this._lastCompanyTurnSummary = { brief: stagingBrief, reportTail: tail, @@ -2654,6 +2676,53 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn // turn이 끝났으면(완료든 abort든) resume 가능 세션 목록을 새로 푸시 — // 방금 abort된 세션이 곧장 목록에 떠야 하므로. void this._sendCompanyResumable(); + // 완료된 회사 turn을 채팅 기록(기록 목록)에도 한 줄로 남긴다 — 보고서가 + // 나온 경우(report-done)에만. abort돼서 보고서가 없으면 기록 목록은 + // 건드리지 않고 resume 목록에만 남는다. + if (finalReport) { + void this._saveCompanyTurnSession(userPrompt, finalReport); + } + } + } + + /** + * Records a completed 1인 기업 turn as a standalone entry in the 기록(Chat + * History) list. Company turns run through the dispatcher, not AgentExecutor, + * so they never populate `_agent` history and `_saveCurrentSession()` can't + * see them — without this they only ever appeared in the resume list. + * + * Each turn gets its own entry (it doesn't touch `_currentSessionId`): a + * dispatch is a discrete unit of work, not a back-and-forth chat thread. + */ + async _saveCompanyTurnSession(userPrompt: string, report: string) { + try { + const prompt = (userPrompt || '').trim(); + const body = (report || '').trim(); + if (!prompt && !body) return; + + const history: ChatMessage[] = [ + { role: 'user', content: prompt || '(요청 내용 없음)' }, + { role: 'assistant', content: body || '(보고서가 생성되지 않았습니다.)' }, + ]; + const title = prompt + ? prompt.substring(0, 50).replace(/\n/g, ' ') + (prompt.length > 50 ? '...' : '') + : '1인 기업 업무'; + const brainProfileId = this._currentSessionBrainId || getActiveBrainProfile().id; + + let sessions = this._getSessions(); + sessions.unshift({ + id: Date.now().toString(), + title, + timestamp: Date.now(), + history, + brainProfileId, + negativePrompt: this._currentNegativePrompt, + }); + if (sessions.length > 50) sessions = sessions.slice(0, 50); + await this._putSessions(sessions); + await this._sendSessionList(); + } catch (err: any) { + logError('Failed to record company turn into chat history.', { error: err?.message ?? String(err) }); } }