diff --git a/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json b/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json index c1057b6..9e57e5b 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": 1777985740400, + "createdAt": 1778033752470, "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 ddacc5f..ef1ec7f 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": 1777985740393, + "createdAt": 1778033752468, "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 a2ce4c4..1bcd65a 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": 1777985740387, + "createdAt": 1778033752466, "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 714105c..d2c00bf 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_1777985740371\ndate: 2026-05-05T12:55:40.404Z\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]** 핵심 정보 수집 및 분석 중... (4ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (9ms)\n", - "createdAt": 1777985740404, + "result": "---\nid: stress_conflict_1778033752447\ndate: 2026-05-06T02:15:52.471Z\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]** 전략 수립 중... (18ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (2ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (2ms)\n", + "createdAt": 1778033752471, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/missions/stress_conflict_1777985740371.json b/.astra/tests/stress/.astra/missions/stress_conflict_1778033752447.json similarity index 78% rename from .astra/tests/stress/.astra/missions/stress_conflict_1777985740371.json rename to .astra/tests/stress/.astra/missions/stress_conflict_1778033752447.json index 43723e0..1c472f4 100644 --- a/.astra/tests/stress/.astra/missions/stress_conflict_1777985740371.json +++ b/.astra/tests/stress/.astra/missions/stress_conflict_1778033752447.json @@ -1,8 +1,8 @@ { - "missionId": "stress_conflict_1777985740371", + "missionId": "stress_conflict_1778033752447", "status": "completed", - "startTime": "2026-05-05T12:55:40.371Z", - "totalElapsedMs": 33, + "startTime": "2026-05-06T02:15:52.447Z", + "totalElapsedMs": 25, "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": 12, + "durationMs": 18, "message": "전략 수립 중...", - "ts": "2026-05-05T12:55:40.383Z" + "ts": "2026-05-06T02:15:52.465Z" }, { "from": "planner", "to": "researcher", - "durationMs": 4, + "durationMs": 2, "message": "핵심 정보 수집 및 분석 중...", - "ts": "2026-05-05T12:55:40.387Z" + "ts": "2026-05-06T02:15:52.467Z" }, { "from": "researcher", "to": "writer", - "durationMs": 9, + "durationMs": 2, "message": "최종 리포트 작성 및 편집 중...", - "ts": "2026-05-05T12:55:40.396Z" + "ts": "2026-05-06T02:15:52.469Z" }, { "from": "writer", "to": "completed", - "durationMs": 8, + "durationMs": 3, "message": "미션 완료", - "ts": "2026-05-05T12:55:40.404Z" + "ts": "2026-05-06T02:15:52.472Z" } ], "resilienceMetrics": { diff --git a/package-lock.json b/package-lock.json index d3ba2d0..d0688f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { - "name": "g1nation", + "name": "astra", "version": "2.78.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "g1nation", + "name": "astra", "version": "2.78.0", "license": "MIT", "dependencies": { - "marked": "^18.0.2" + "marked": "^18.0.2", + "pdf-parse": "^2.4.5" }, "devDependencies": { "@types/jest": "^29.5.14", @@ -57,6 +58,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1333,6 +1335,190 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz", + "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==", + "license": "MIT", + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.80", + "@napi-rs/canvas-darwin-arm64": "0.1.80", + "@napi-rs/canvas-darwin-x64": "0.1.80", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.80", + "@napi-rs/canvas-linux-arm64-musl": "0.1.80", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-musl": "0.1.80", + "@napi-rs/canvas-win32-x64-msvc": "0.1.80" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz", + "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz", + "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz", + "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz", + "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz", + "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz", + "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz", + "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz", + "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz", + "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz", + "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.10", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", @@ -1757,6 +1943,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -2654,6 +2841,7 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -3662,6 +3850,38 @@ "dev": true, "license": "MIT" }, + "node_modules/pdf-parse": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-2.4.5.tgz", + "integrity": "sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==", + "license": "Apache-2.0", + "dependencies": { + "@napi-rs/canvas": "0.1.80", + "pdfjs-dist": "5.4.296" + }, + "bin": { + "pdf-parse": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.16.0 <21 || >=22.3.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/mehmet-kozan" + } + }, + "node_modules/pdfjs-dist": { + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4178,6 +4398,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 8969c04..bb546d3 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.78.0", + "version": "2.79.0", "publisher": "g1nation", "license": "MIT", "icon": "assets/icon.png", @@ -226,6 +226,7 @@ "typescript": "^5.1.3" }, "dependencies": { - "marked": "^18.0.2" + "marked": "^18.0.2", + "pdf-parse": "^2.4.5" } } diff --git a/src/agent.ts b/src/agent.ts index 0a52ca7..bc6d369 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -333,9 +333,8 @@ export class AgentExecutor { } } - // 3. API Request Setup - const { ollamaUrl, defaultModel: configDefaultModel, timeout } = getConfig(); - const actualModel = modelName || configDefaultModel; + // 3. API Request Setup (라인 229에서 이미 추출한 ollamaUrl, configDefaultModel 재사용) + const actualModel = (modelName && modelName.trim()) || configDefaultModel; const reqMessages = this.buildRequestHistory(this.chatHistory); // Handle Vision Content Injection diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 63d9d9a..db2c213 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -74,6 +74,16 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn localResourceRoots: [this._extensionUri] }; + // [State Persistence Fix] 사이드바가 다시 보여질 때 세팅값 자동 복원 + webviewView.onDidChangeVisibility(() => { + if (webviewView.visible) { + logInfo('Sidebar became visible, restoring state...'); + void this._sendModels(); + void this._sendBrainProfiles(); + void this._sendAgentsList(); + } + }); + webviewView.webview.html = this._getHtml(webviewView.webview); this._agent.setWebview(webviewView.webview); @@ -204,6 +214,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn case 'deleteAgent': await this._deleteAgent(data.path); break; + case 'saveAgentSelection': + await this._context.globalState.update(SidebarChatProvider.lastAgentStateKey, data.path || 'none'); + logInfo(`Agent selection saved: ${data.path}`); + break; case 'refreshModels': await this._sendModels(); break; @@ -1824,10 +1838,75 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn const designerContext = designerGuard !== false ? this._buildDesignerGuardContext() : undefined; + // [File Processing v2] 파일 타입별 분류 처리 + let processedPrompt = value || ''; + let imageFiles: any[] | undefined = undefined; + + if (files && Array.isArray(files) && files.length > 0) { + const textContents: string[] = []; + const images: any[] = []; + + for (const file of files) { + const name = file.name?.toLowerCase() || ''; + const type = file.type || ''; + + if (name.endsWith('.pdf') || type === 'application/pdf') { + // PDF: 서버사이드 텍스트 추출 (pdf-parse v2 API) + try { + const { PDFParse } = require('pdf-parse'); + const rawBuffer = Buffer.from(file.data, 'base64'); + const uint8 = new Uint8Array(rawBuffer.buffer, rawBuffer.byteOffset, rawBuffer.byteLength); + const parser = new PDFParse(uint8); + await parser.load(); + const textResult = await parser.getText(); + // pdf-parse v2: getText() returns {pages: [{text, num}], text: string, total: number} + const extracted = (typeof textResult === 'string' ? textResult : (textResult?.text || '')).trim(); + // 페이지 구분 마커 제거하여 깔끔한 텍스트 추출 + const cleanText = extracted.replace(/\n*-- \d+ of \d+ --\n*/g, '\n').trim(); + if (cleanText && cleanText.length > 10) { + textContents.push(`\n[PDF: ${file.name}]\n${cleanText}`); + logInfo(`PDF text extracted successfully.`, { fileName: file.name, chars: cleanText.length }); + } else { + textContents.push(`\n[PDF: ${file.name}]\n(텍스트 추출 결과 없음 - 이미지 기반 PDF일 수 있습니다. 텍스트 레이어가 없는 스캔 문서는 OCR 변환 후 재시도하세요.)`); + logInfo(`PDF text extraction returned empty/minimal result.`, { fileName: file.name, rawLength: extracted.length }); + } + } catch (pdfError: any) { + logError(`PDF parsing failed.`, { fileName: file.name, error: pdfError?.message || String(pdfError) }); + textContents.push(`\n[PDF: ${file.name}]\n(PDF 파싱 오류: ${pdfError?.message || '알 수 없는 오류'})`); + } + } else if ( + type.startsWith('text/') || + type === 'application/json' || + /\.(txt|md|csv|json|js|ts|jsx|tsx|html|css|py|java|rs|go|yaml|yml|xml|toml|sql|sh)$/i.test(name) + ) { + // 텍스트 파일: base64 디코딩 + try { + const decoded = Buffer.from(file.data, 'base64').toString('utf-8'); + textContents.push(`\n[FILE: ${file.name}]\n\`\`\`\n${decoded}\n\`\`\``); + } catch (decodeError: any) { + logError(`Text file decode failed.`, { fileName: file.name, error: decodeError?.message }); + textContents.push(`\n[FILE: ${file.name}]\n(디코딩 오류)`); + } + } else if (type.startsWith('image/')) { + // 이미지: 기존 vision 방식 유지 + images.push(file); + } else { + // 미지원 타입: 파일명만 기록 + textContents.push(`\n[ATTACHMENT: ${file.name}]\n(지원하지 않는 파일 형식: ${type || 'unknown'})`); + } + } + + // 추출된 텍스트를 프롬프트에 주입 + if (textContents.length > 0) { + processedPrompt = `${processedPrompt}\n\n--- 첨부 파일 내용 ---${textContents.join('\n')}`; + } + imageFiles = images.length > 0 ? images : undefined; + } + try { - await this._agent.handlePrompt(value, model, { + await this._agent.handlePrompt(processedPrompt, model, { internetEnabled: internet, - visionContent: files, + visionContent: imageFiles, agentSkillContext, negativePrompt, designerContext, @@ -1894,19 +1973,15 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn await vscode.workspace.getConfiguration('g1nation').update('defaultModel', defaultModel, vscode.ConfigurationTarget.Global); } - if (models.length > 0 && (!defaultModel || !models.includes(defaultModel))) { - // [State Persistence Fix] 저장된 모델이 목록에 없을 때: - // 즉시 강제 리셋하는 대신, 현재 모델 목록의 첫 번째를 '폴백 후보'로만 사용. - // 단, defaultModel이 완전히 없는 경우에만 실제로 저장함. - if (!defaultModel) { - defaultModel = models[0]; - await vscode.workspace.getConfiguration('g1nation').update('defaultModel', defaultModel, vscode.ConfigurationTarget.Global); - } else { - // 저장된 모델명은 유지하고, UI에는 첫 번째 모델을 보여주되 - // 설정은 건드리지 않아 다음 번에 같은 모델이 다시 로드될 경우 복원 가능하도록 함 - logInfo('Saved model not found in current list, using first available as fallback.', { saved: defaultModel, fallback: models[0] }); - defaultModel = models[0]; - } + if (models.length > 0 && !defaultModel) { + // [State Persistence Fix v2] defaultModel이 완전히 비어있을 때만 첫 번째 모델로 설정 + defaultModel = models[0]; + await vscode.workspace.getConfiguration('g1nation').update('defaultModel', defaultModel, vscode.ConfigurationTarget.Global); + } else if (models.length > 0 && defaultModel && !models.includes(defaultModel)) { + // [State Persistence Fix v2] 저장된 모델이 로컬 엔진 목록에 없는 경우: + // 강제 리셋하지 않고, 저장된 모델을 목록 선두에 추가하여 사용자 선택을 보존 + logInfo('Saved model not in local engine list. Preserving user selection.', { saved: defaultModel, localModels: models.slice(0, 3) }); + models.unshift(defaultModel); } const defaultIdx = models.indexOf(defaultModel); @@ -3309,6 +3384,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn agentSel.onchange = () => { if (agentSel.value !== 'none') { vscode.postMessage({ type: 'getAgentContent', path: agentSel.value }); + // [State Persistence Fix] 에이전트 선택값을 즉시 백엔드에 저장 + vscode.postMessage({ type: 'saveAgentSelection', path: agentSel.value }); if (editMode) agentConfigPanel.style.display = 'flex'; } else { agentConfigPanel.style.display = 'none'; @@ -3316,6 +3393,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn editAgentBtn.classList.remove('active'); agentPrompt.value = ''; negativePrompt.value = ''; + // [State Persistence Fix] 에이전트 해제도 즉시 저장 + vscode.postMessage({ type: 'saveAgentSelection', path: 'none' }); } }; diff --git a/src/utils.ts b/src/utils.ts index 28f4358..6b3db66 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -154,7 +154,7 @@ Never say "upload the source code", "provide the files", or "파일 내용을 If access fails after trying, explain the failure and only then ask for an upload. [STRICT GLOBAL RULES] -1. [NO EMOJIS] Never use emojis, icons, or pictorial symbols. Professional text-only output. Exception: the 💡 icon in the suggestion header only. +1. [NO EMOJIS - ABSOLUTE RULE] NEVER use ANY emojis, emoticons, Unicode pictorial symbols (including but not limited to emoji, kaomoji, Unicode icons), or decorative symbols anywhere in your response. NO EXCEPTIONS. Use plain text dashes (-) or asterisks (*) for bullets. Use plain markdown ## for headers. This rule overrides ALL other formatting instructions. 2. [UNIQUE HEADINGS] Every markdown heading must be unique and appear exactly once. 3. [NO INTERNAL LOGS] Never output
, "2nd Brain Trace", or "Debug JSON" blocks. 4. [NO SECTION LEAKAGE] Never output sections named "요청 요약", "사용자 의도 추론", "프로젝트 기록 대상 확인", "핵심 확인 질문", or "근거 파일 경로". @@ -170,7 +170,7 @@ For conversational replies, quick facts, or simple updates — answer directly w - Root cause of the problem. - Concrete step-by-step instructions: what to change, which files to edit, which commands to run. - ## 💡 제안 ← Optional. Only include if a meaningfully better alternative exists. Omit otherwise. + ## 제안 ← Optional. Only include if a meaningfully better alternative exists. Omit otherwise. [FOLLOW-UP QUESTION RULES] A follow-up question is a precision tool, not a ritual. @@ -228,7 +228,10 @@ If neither condition is met, give a definitive answer and stop. 3. When the user says "분석해줘", "봐줘", "확인해줘", "리뷰해줘" with a path — that IS the confirmation. Access the path immediately and run the full analysis in one continuous response. Do not pause to ask "진행할까요?" or "시작할까요?".`; export function getSystemPrompt(): string { - return BASE_SYSTEM_PROMPT; + const now = new Date(); + const dateTimeStr = now.toLocaleString('ko-KR', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit', weekday: 'long', hour: '2-digit', minute: '2-digit' }); + const isoDate = now.toISOString().split('T')[0]; + return `${BASE_SYSTEM_PROMPT}\n\n[CURRENT DATE/TIME]\nToday: ${isoDate} (${dateTimeStr})\nUse this date as the absolute reference for any date-related calculations (e.g., "this week", "today", "yesterday").`; } export const SYSTEM_PROMPT = BASE_SYSTEM_PROMPT; diff --git a/tests/localPathPreflight.test.ts b/tests/localPathPreflight.test.ts index b629ba5..05624b0 100644 --- a/tests/localPathPreflight.test.ts +++ b/tests/localPathPreflight.test.ts @@ -40,7 +40,7 @@ describe('local project path preflight', () => { expect(inspected).toContain('Access: succeeded'); expect(inspected).toContain('Priority file previews'); expect(inspected).toContain('package.json'); - expect(inspected).toContain('src/features/game/systems/CombatSystem.ts'); + expect(inspected).toContain(path.join('src', 'features', 'game', 'systems', 'CombatSystem.ts')); expect(inspected).toContain('export class CombatSystem'); }); diff --git a/tests/projectChronicle.test.ts b/tests/projectChronicle.test.ts index 8e30e7b..3c3edd0 100644 --- a/tests/projectChronicle.test.ts +++ b/tests/projectChronicle.test.ts @@ -155,11 +155,11 @@ describe('ProjectChronicleManager', () => { expect(fs.existsSync(result.filePath)).toBe(true); } - expect(discussion.relativePath).toContain('discussions/'); - expect(decision.relativePath).toContain('decisions/'); - expect(development.relativePath).toContain('development/'); - expect(bug.relativePath).toContain('bugs/'); - expect(retrospective.relativePath).toContain('retrospectives/'); + expect(discussion.relativePath).toContain('discussions' + path.sep); + expect(decision.relativePath).toContain('decisions' + path.sep); + expect(development.relativePath).toContain('development' + path.sep); + expect(bug.relativePath).toContain('bugs' + path.sep); + expect(retrospective.relativePath).toContain('retrospectives' + path.sep); }); it('lists chronicle records across sections with relative paths', () => { @@ -191,8 +191,8 @@ describe('ProjectChronicleManager', () => { expect(records.length).toBe(2); expect(records.map(record => record.relativePath)).toEqual( expect.arrayContaining([ - 'planning/2026-05-02_record-list.md', - 'bugs/BUG-0001-list-bug.md' + path.join('planning', '2026-05-02_record-list.md'), + path.join('bugs', 'BUG-0001-list-bug.md') ]) ); });