import * as vscode from 'vscode'; import * as path from 'path'; import { SidebarChatProvider } from '../sidebarProvider'; import { getActiveBrainProfile, logInfo } from '../utils'; import { pickConfigTarget } from '../lib/paths'; /** * Handles chat-domain messages: prompts, model selection, sessions, streaming control, * generic webview transport (export, settings, addMessage), action approvals, and the * cross-cutting `ready` bootstrap. * * Returns true when the message was handled by this domain, false otherwise โ€” the * caller chains domain handlers until one accepts the message. */ export async function handleChatMessage(provider: SidebarChatProvider, data: any): Promise { switch (data.type) { case 'prompt': case 'promptWithFile': logInfo(`[ASTRA-DEBUG] prompt case entered type=${data?.type} value=${JSON.stringify(String(data?.value ?? '').slice(0, 80))}`); provider._lmStudio?.activity.bump(); // โ”€โ”€ ๐Ÿ“ป Datacollect Radio (slash ๋ช…๋ น) ์šฐ์„  ๋ถ„๊ธฐ โ”€โ”€ // ์ฃผ์˜: globalState.update๋ณด๋‹ค *๋จผ์ €* ์žก๋Š”๋‹ค โ€” ๊ธ€๋กœ๋ฒŒ state๊ฐ€ ~1MB๊นŒ์ง€ // ๋ˆ„์ ๋œ ํ™˜๊ฒฝ์—์„œ update๊ฐ€ ๋А๋ ค ์ฒซ prompt๊ฐ€ hangํ•˜๋Š” ์‚ฌ๋ก€ ๋ณด๊ณ ๋จ. slash // ๋ช…๋ น์€ LLM์„ ์šฐํšŒํ•˜๋‹ˆ blank chat state ๊ฐฑ์‹ ๋„ ํ•„์š” ์—†์Œ. if (typeof data.value === 'string') { const { isSlashCommand, handleSlashCommand } = await import('../features/datacollect/slashRouter'); const matched = isSlashCommand(data.value); logInfo(`[ASTRA-DEBUG] slash check matched=${matched} hasView=${!!provider._view}`); logInfo(`[SLASH] prompt received: ${JSON.stringify(data.value).slice(0, 100)} matched=${matched} hasView=${!!provider._view}`); if (matched) { if (!provider._view?.webview) { const msg = '๐Ÿ“ป Datacollect Radio: ์ฑ„ํŒ… webview๊ฐ€ ํ™œ์„ฑ ์ƒํƒœ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. Astra ์‚ฌ์ด๋“œ๋ฐ”๋ฅผ ํ•œ ๋ฒˆ ์—ด๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•ด ์ฃผ์„ธ์š”.'; await vscode.window.showWarningMessage(msg); logInfo(`[SLASH] webview not available โ€” aborting`); return true; } logInfo(`[SLASH] handleSlashCommand entering`); // Slash ๋ช…๋ น ๊ฒฐ๊ณผ๋ฅผ *chatHistory ์—๋„* mirror โ€” ๋‹ค์Œ turn ์˜ LLM ์ด // ์ง์ „ ๋ช…๋ น ์ถœ๋ ฅ (์˜ˆ: /stocks discover ๋ฐœ๊ตด ๊ฒฐ๊ณผ) ์„ ์ปจํ…์ŠคํŠธ๋กœ ๋ณด๊ฒŒ ํ•œ๋‹ค. // ์•ˆ ํ•˜๋ฉด LLM ์€ webview UI ๋งŒ ๋ณด๊ณ  history ๋Š” ๋น„์–ด ์žˆ์–ด์„œ "์ฃผ์‹ ๋ฐ์ดํ„ฐ // ์—†์Œ" ๊ฐ™์€ ํ™˜๊ฐ ์‘๋‹ต์ด ๋‚˜์˜ด. const realWebview = provider._view.webview; const captured: string[] = []; const captureWebview = { postMessage: (msg: any) => { if (msg && msg.type === 'streamChunk' && typeof msg.value === 'string') { captured.push(msg.value); } return realWebview.postMessage(msg); }, }; await handleSlashCommand(data.value, captureWebview as any, provider._context); const collected = captured.join('').trim(); if (collected) { // CRITICAL: getHistory() ๋Š” *filtered copy* ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ์ง์ ‘ push ํ•˜๋ฉด // ์›๋ณธ chatHistory ์— ์•ˆ ๋“ค์–ด๊ฐ (silent fail). ์ƒˆ ๋ฐฐ์—ด๋กœ setHistory. const current = provider._agent.getHistory(); const updated = [ ...current, { role: 'user' as const, content: data.value }, { role: 'assistant' as const, content: collected, internal: false }, ]; provider._agent.setHistory(updated); } logInfo(`[SLASH] handleSlashCommand returned, captured ${collected.length} chars โ†’ history len ${provider._agent.getHistory().length}`); return true; } } await provider._sessionState.setBlankChatActive(false); // โ”€โ”€ 1์ธ ๊ธฐ์—… ๋ชจ๋“œ ์šฐ์„  ๋ถ„๊ธฐ โ”€โ”€ // ํšŒ์‚ฌ ๋ชจ๋“œ์ผ ๋• ๋“ค์–ด์˜จ ๋ฉ”์‹œ์ง€๋ฅผ intent classifier์— ํ•œ ๋ฒˆ ํ†ต๊ณผ์‹œ์ผœ // (a) ์žก๋‹ด/์งˆ๋ฌธ/์งง์€ ์‘๋‹ต โ†’ ์ผ๋ฐ˜ ์ฑ„ํŒ… ๊ฒฝ๋กœ, (b) ํ›„์† ๋Œ€ํ™” โ†’ ์ผ๋ฐ˜ ์ฑ„ํŒ… // ๊ฒฝ๋กœ + followup ๋ผ๋ฒจ, (c) ์‹ ๊ทœ ์—…๋ฌด โ†’ ํ’€ ํŒŒ์ดํ”„๋ผ์ธ dispatch. // classifier๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋ผ ์žˆ๊ฑฐ๋‚˜ ํ˜ธ์ถœ ์‹คํŒจ ์‹œ์—” ์•ˆ์ „ํ•˜๊ฒŒ new_task๋กœ // ํด๋ฐฑ โ€” ์ฆ‰ ์‚ฌ์šฉ์ž๊ฐ€ ์˜๋„ํ•œ ์ž‘์—… ์š”์ฒญ์„ ๋†“์น˜๋Š” ์ผ์€ ์ ˆ๋Œ€ ์—†๋‹ค. if (provider.isCompanyModeEnabled() && typeof data.value === 'string' && data.value.trim()) { let userPrompt = data.value.trim(); const { getConfig } = await import('../config'); const cfg = getConfig(); const { readCompanyState, resolveActivePipeline } = await import('../features/company'); const state = readCompanyState(provider._context); // โ”€โ”€ alignment ๋‹ต๋ณ€ ๋ผ์šฐํŒ… โ”€โ”€ // ์‚ฌ์šฉ์ž๊ฐ€ ์ด์ „ ๋ฉ”์‹œ์ง€์—์„œ alignment ์นด๋“œ๋ฅผ ๋ฐ›์•„ ๋‹ต๋ณ€ํ•˜๋Š” ์ค‘์ด๋ฉด // ์ด ๋ฉ”์‹œ์ง€๋ฅผ ๋ถ„๋ฅ˜๊ธฐ/dispatch๊ฐ€ ์•„๋‹ˆ๋ผ alignment ๋‹ต๋ณ€ ํ•ธ๋“ค๋Ÿฌ๋กœ // ๋ณด๋‚ธ๋‹ค. ๋‹ต๋ณ€์ด ์ฒ˜๋ฆฌ๋˜๋ฉด์„œ ์ž๋™์œผ๋กœ ๋‹ค์Œ ๋ผ์šด๋“œ ๋˜๋Š” pipeline // ์œผ๋กœ ์ง„ํ–‰๋จ. if (provider.isAlignmentPending()) { await provider._handleAlignmentAnswer(userPrompt); return true; } // โ”€โ”€ ์‚ฌ์šฉ์ž ํ‚ค์›Œ๋“œ override โ”€โ”€ // ์ž…๋ ฅ ๋งจ ์•ž์— `[ํŒŒ์ดํ”„๋ผ์ธ:id]` ๋˜๋Š” `[pipeline:id]`๊ฐ€ ์žˆ์œผ๋ฉด // ๋ถ„๋ฅ˜๊ธฐ ๋ฌด๊ด€ํ•˜๊ฒŒ ๊ทธ ํŒŒ์ดํ”„๋ผ์ธ ๊ฐ•์ œ + ๊ทธ ํ‚ค์›Œ๋“œ๋Š” prompt์—์„œ // ์ œ๊ฑฐ ํ›„ dispatcher์— ์ „๋‹ฌ. id๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฌด์‹œ(๋ถ„๋ฅ˜๊ธฐ ์ •์ƒ ๊ฒฝ๋กœ). let keywordOverrideId: string | undefined; const keywordMatch = userPrompt.match(/^\s*\[(?:ํŒŒ์ดํ”„๋ผ์ธ|pipeline)\s*:\s*([a-z0-9_-]+)\s*\]\s*/i); if (keywordMatch) { const id = keywordMatch[1].toLowerCase(); if (state.pipelines?.[id]) { keywordOverrideId = id; userPrompt = userPrompt.slice(keywordMatch[0].length).trim() || userPrompt; } } // โ”€โ”€ alignment bypass ํ‚ค์›Œ๋“œ โ”€โ”€ // ์ž…๋ ฅ ๋งจ ์•ž `[๊ฑด๋„ˆ๋›ฐ๊ธฐ]` ๋˜๋Š” `[skip]` โ†’ alignment ๋‹จ๊ณ„ 1ํšŒ ์šฐํšŒ. // ์‚ฌ์šฉ์ž๊ฐ€ "์ง€๊ธˆ์€ ๋นจ๋ฆฌ ๊ฐ€์ž"๋ผ๊ณ  ๋ช…์‹œํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉ. prompt์—์„œ // ํ‚ค์›Œ๋“œ ์ œ๊ฑฐ. let alignmentBypass = false; const bypassMatch = userPrompt.match(/^\s*\[(?:๊ฑด๋„ˆ๋›ฐ๊ธฐ|skip)\]\s*/i); if (bypassMatch) { alignmentBypass = true; userPrompt = userPrompt.slice(bypassMatch[0].length).trim() || userPrompt; } if (cfg.companyDisableIntentClassifier) { // ๋ถ„๋ฅ˜๊ธฐ ์šฐํšŒ ๋ชจ๋“œ โ€” ๋ถ„๋ฅ˜ ๋‹จ๊ณ„๋Š” ๊ฑด๋„ˆ๋›ฐ์ง€๋งŒ alignment๋Š” ๋ณ„๋„๋กœ // ์ž‘๋™(์‚ฌ์šฉ์ž๊ฐ€ alignment off๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์€ ํ•œ). ๋ถ„๋ฅ˜๊ธฐ ๋„๋Š” // ์ด์œ ๋Š” ๋ณดํ†ต "์žก๋‹ด๋„ ๋‹ค pipeline์œผ๋กœ"์ธ๋ฐ ๊ทธ๋Ÿด์ˆ˜๋ก alignment // ํšจ๊ณผ๊ฐ€ ํผ. try { provider.pixelOfficeOnIntentClassified('new_task', userPrompt); } catch { /* noop */ } if (cfg.companyIntentAlignmentMode === 'off' || alignmentBypass) { await provider._runCompanyTurn(userPrompt, undefined, keywordOverrideId); } else { await provider._runIntentAlignment({ userPrompt, pipelineIdOverride: keywordOverrideId, mode: cfg.companyIntentAlignmentMode === 'strict' ? 'strict' : 'smart', roundsLimit: cfg.companyIntentAlignmentMaxRounds, roundsAsked: 0, }); } return true; } const { classifyChatIntent } = await import('../features/company'); const { AIService } = await import('../core/services'); const last = provider.getLastCompanyTurnSummary(); const activePipeline = resolveActivePipeline(state); // ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ํŒŒ์ดํ”„๋ผ์ธ์„ ๋ถ„๋ฅ˜๊ธฐ ํ›„๋ณด๋กœ ์ „๋‹ฌ โ€” ๋‹จ, ํ™œ์„ฑํ™”๋ผ // ์žˆ์–ด์•ผ ์ถ”์ฒœ ์˜๋ฏธ๊ฐ€ ์žˆ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ *์ •์˜๋ผ ์žˆ๊ธฐ๋งŒ ํ•˜๋ฉด* ํ›„๋ณด. ์‚ฌ์šฉ์ž๊ฐ€ // ํ‰์†Œ์—” ์งง์€ ๊ฑธ ํ™œ์„ฑํ™”ํ•ด ๋‘๊ณ  ๊ฐ€๋” ํ’€ ์‚ฌ์ดํด ์˜๋„๊ฐ€ ๋ช…ํ™•ํ•œ ๋ฐœํ™”๋ฅผ // ํ–ˆ์„ ๋•Œ ๋ถ„๋ฅ˜๊ธฐ๊ฐ€ ๊ทธ์ชฝ์„ ์ถ”์ฒœํ•  ์ˆ˜ ์žˆ๊ฒŒ. const allPipelines = Object.values(state.pipelines ?? {}); const verdict = await classifyChatIntent( new AIService(), userPrompt, { previousBrief: last?.brief, previousReportTail: last?.reportTail, previousTurnAt: last?.finishedAt, activePipelineName: activePipeline?.name, availablePipelines: allPipelines.length > 0 ? allPipelines.map((p) => ({ id: p.id, name: p.name, stageCount: p.stages.length, })) : undefined, }, { model: cfg.companyIntentClassifierModel || cfg.defaultModel }, ); // Pixel Office: ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ๋ฅผ UI layer๋กœ๋งŒ ํ˜๋ฆผ. ์•„๋ž˜ ๋ถ„๊ธฐ ์ž์ฒด์—” ์˜ํ–ฅ ์—†์Œ. try { provider.pixelOfficeOnIntentClassified(verdict.intent, userPrompt); } catch { /* noop */ } if (verdict.intent === 'new_task') { // ์šฐ์„ ์ˆœ์œ„: (1) ์‚ฌ์šฉ์ž ํ‚ค์›Œ๋“œ (2) autoSelect๊ฐ€ ์ผœ์ ธ ์žˆ๊ณ  ๋ถ„๋ฅ˜๊ธฐ ์ถ”์ฒœ ์žˆ์Œ (3) ์‚ฌ์šฉ์ž ํ™œ์„ฑ ํŒŒ์ดํ”„๋ผ์ธ. let effectiveOverride = keywordOverrideId; if (!effectiveOverride && cfg.companyAutoSelectPipeline && verdict.suggestedPipelineId) { effectiveOverride = verdict.suggestedPipelineId; } // ๋ถ„๋ฅ˜๊ธฐ๊ฐ€ ์ถ”์ฒœ์„ ๋ƒˆ์ง€๋งŒ autoSelect๊ฐ€ ๊บผ์ ธ ์žˆ์„ ๋• ๋ผ๋ฒจ๋กœ๋งŒ ์•ˆ๋‚ด. if (verdict.suggestedPipelineId && !effectiveOverride && !cfg.companyAutoSelectPipeline) { const tip = state.pipelines?.[verdict.suggestedPipelineId]; if (tip) { provider._view?.webview.postMessage({ type: 'companyIntentDecision', value: { intent: 'new_task', reason: `๐Ÿงญ ์ถ”์ฒœ ํŒŒ์ดํ”„๋ผ์ธ: "${tip.name}" (์ž๋™ ์ ์šฉ์€ ์„ค์ • ํ† ๊ธ€)`, label: '๐Ÿ› ๏ธ ์‹ ๊ทœ ์—…๋ฌด', }, }); } } else if (effectiveOverride && effectiveOverride !== state.activePipelineId) { const used = state.pipelines?.[effectiveOverride]; if (used) { provider._view?.webview.postMessage({ type: 'companyIntentDecision', value: { intent: 'new_task', reason: keywordOverrideId ? `๐Ÿ”ง ํ‚ค์›Œ๋“œ override โ†’ "${used.name}"` : `๐Ÿงญ CEO ์ž๋™ ์„ ํƒ โ†’ "${used.name}"`, label: '๐Ÿ› ๏ธ ์‹ ๊ทœ ์—…๋ฌด', }, }); } } // โ”€โ”€ Intent Alignment ์ง„์ž… โ”€โ”€ // off ๋ชจ๋“œ์ด๊ฑฐ๋‚˜ bypass ํ‚ค์›Œ๋“œ๊ฐ€ ์žˆ์œผ๋ฉด alignment ์šฐํšŒํ•˜๊ณ  // legacy ๋™์ž‘ (์ฆ‰์‹œ dispatch). ๊ทธ ์™ธ์—” ๋ถ„์„๊ธฐ 1๋ผ์šด๋“œ ๋Œ๋ ค // confidence์— ๋”ฐ๋ผ ์ž๋™ ์ง„ํ–‰ ๋˜๋Š” ์นด๋“œ ํ‘œ์‹œ. if (cfg.companyIntentAlignmentMode === 'off' || alignmentBypass) { await provider._runCompanyTurn(userPrompt, undefined, effectiveOverride); } else { await provider._runIntentAlignment({ userPrompt, pipelineIdOverride: effectiveOverride, mode: cfg.companyIntentAlignmentMode === 'strict' ? 'strict' : 'smart', roundsLimit: cfg.companyIntentAlignmentMaxRounds, roundsAsked: 0, }); } } else { await provider._handleCompanyCasual(userPrompt, verdict.intent, verdict.reason, data); } return true; } 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(); return true; case 'ready': await provider._sendBrainStatus(); await provider._sendBrainProfiles(); await provider._sendSessionList(); await provider._sendModels(); await provider._sendChronicleProjects(); await provider._restoreActiveSessionIntoView(); await provider._sendReadyStatus(); // Slash ๋ช…๋ น ๋ชฉ๋ก โ€” webview ์˜ `/` ์ž๋™์™„์„ฑ dropdown ์ด ์‚ฌ์šฉ. try { const { listSlashCommands } = await import('../features/datacollect/slashRouter'); const cmds = listSlashCommands().map(c => ({ name: c.name, description: c.description || '' })); provider._view?.webview.postMessage({ type: 'slashCommandList', commands: cmds }); } catch (e: any) { // Slash ๋ผ์šฐํ„ฐ ๋กœ๋“œ ์‹คํŒจํ•ด๋„ ์ฑ„ํŒ… ์ž์ฒด๋Š” ๋™์ž‘ํ•ด์•ผ โ€” silent skip. } // Restore the Project Architecture chip + watcher if the active project // was already running in architecture mode in a previous VS Code session. await provider._sendArchitectureStatus(); // Restore the Company chip from globalState so the user sees the same // mode they had on at last shutdown. await provider._sendCompanyStatus(); // Pixel Office โ€” ์ฒซ ๋กœ๋“œ ์‹œ ๋นˆ idle ์ƒํƒœ๋ผ๋„ ํ•œ ๋ฒˆ pushํ•ด์„œ webview๊ฐ€ // ์˜์—ญ ์ž์ฒด๋ฅผ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๊ฒŒ. provider.pixelOfficeResend(); return true; case 'getReadyStatus': await provider._sendReadyStatus(); return true; case 'createLessonFromConversation': await vscode.commands.executeCommand('g1nation.lesson.fromConversation'); return true; case 'manageLessons': await vscode.commands.executeCommand('g1nation.lesson.manage'); return true; case 'getModels': await provider._sendModels(); return true; case 'getSessions': await provider._sendSessionList(); return true; case 'newChat': provider._currentSessionId = null; provider._currentSessionBrainId = getActiveBrainProfile().id; provider._agent.resetConversation(); await provider._sessionState.setActiveSessionId(null); await provider._sessionState.setLastVisibleChat(null); await provider._sessionState.setBlankChatActive(true); // ์ง์ „ ํšŒ์‚ฌ turn ์ปจํ…์ŠคํŠธ ์บ์‹œ ๋น„์šฐ๊ธฐ โ€” ์ƒˆ ์„ธ์…˜์€ followup ๊ธฐ์ค€์ ์ด ์—†๋‹ค. provider.clearLastCompanyTurnSummary(); // ์ง„ํ–‰ ์ค‘์ด๋˜ alignment๋„ ์ƒˆ ์„ธ์…˜๊ณผ ํ•จ๊ป˜ ํ๊ธฐ. provider.cancelPendingAlignment(); provider.clearChat(); await provider._sendBrainStatus(); return true; case 'stopGeneration': // 1์ธ ๊ธฐ์—… ๋ชจ๋“œ๋Š” AgentExecutor๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ณ„๋„ abort ๊ฒฝ๋กœ. // ๋‘ ๊ฒฝ๋กœ ๋ชจ๋‘ ์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ด ๋‘๋ฉด ์ค‘๊ฐ„์— ๋ชจ๋“œ ์ „ํ™˜๋˜์–ด๋„ ์•ˆ์ „. provider.abortCompanyTurn(); // ์ง„ํ–‰ ์ค‘์ธ Intent Alignment๋„ ๊ฐ™์ด ์ •๋ฆฌ โ€” ์‚ฌ์šฉ์ž๊ฐ€ Stop ๋ˆ„๋ฅด๋ฉด // ์˜๋„์ƒ ๋ชจ๋“  ๋Œ€๊ธฐ ์ƒํƒœ ํ•ด์ œ. provider.cancelPendingAlignment(); provider._agent.stop(); return true; case 'loadSession': // ์„ธ์…˜ ์ „ํ™˜ ์‹œ ์ง์ „ ํšŒ์‚ฌ turn ์บ์‹œ ๋ฌดํšจํ™” โ€” ๋กœ๋“œ๋œ ์„ธ์…˜์˜ ์ง์ „ ํšŒ์‚ฌ // ์ž‘์—…์€ ๋ณ„๋„ ํŒŒ์ผ์—์„œ ๋ณต์›๋˜์–ด์•ผ ํ•˜์ง€ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ์— ๋‚จ์€ ๋‹ค๋ฅธ // ์„ธ์…˜์˜ ๋ณด๊ณ ์„œ๊ฐ€ ์ƒˆ ์„ธ์…˜ ์ฒซ ๋ฉ”์‹œ์ง€์˜ followup ํŒ์ •์„ ์˜ค์—ผ์‹œํ‚ค๋ฉด ์•ˆ ๋จ. provider.clearLastCompanyTurnSummary(); provider.cancelPendingAlignment(); await provider._loadSession(data.id); return true; case 'deleteSession': await provider._deleteSession(data.id); return true; case 'openSettings': // Route the sidebar gear button to Astra's own settings webview. // Falls back to VS Code Settings if the view hasn't registered yet // (e.g. during the very first activation pass) and surfaces any // unexpected error so the user isn't stuck with a silent button. try { await vscode.commands.executeCommand('g1nation.settings.focus'); } catch (e: any) { logInfo('openSettings: settings.focus failed, falling back to VS Code Settings.', { error: e?.message ?? String(e) }); try { await vscode.commands.executeCommand('workbench.action.openSettings', 'g1nation'); } catch (e2: any) { vscode.window.showErrorMessage(`Astra Settings ์—ด๊ธฐ ์‹คํŒจ: ${e2?.message ?? e2}`); } } return true; case 'addMessage': provider._view?.webview.postMessage({ type: 'addMessage', role: data.role, value: data.value, rationale: data.rationale }); return true; case 'refreshModels': await provider._sendModels(true); return true; case 'model': { // Write to whichever scope already holds the value so a stale // Workspace override doesn't shadow our Global update โ€” that was // the "sidebar shows e2b but Settings shows e4b" desync. const { target } = pickConfigTarget('g1nation', 'defaultModel'); await vscode.workspace.getConfiguration('g1nation').update('defaultModel', data.value, target); logInfo(`Default model updated to: ${data.value}`, { target }); // Wipe any persistent LM Studio error segment from the readyBar โ€” the // next load attempt will repaint it if it also fails. provider.clearLmStudioError(); provider._lmStudio?.lifecycle.onModelSelected(data.value); return true; } case 'getKnowledgeMix': { // Ship the current global Knowledge Mix to the webview so the slider can // initialize. Per-agent overrides ride along with the agent map data. const cfg = vscode.workspace.getConfiguration('g1nation'); const w = cfg.get('knowledgeMix.secondBrainWeight', 50); const clamped = Math.max(0, Math.min(100, Math.round(typeof w === 'number' ? w : 50))); provider._view?.webview.postMessage({ type: 'knowledgeMix', value: { weight: clamped, source: 'global' }, }); return true; } case 'setKnowledgeMix': { const raw = typeof data.value === 'number' ? data.value : NaN; if (!Number.isFinite(raw)) return true; const clamped = Math.max(0, Math.min(100, Math.round(raw))); // Use whichever scope already holds the value to avoid the same "Workspace // override shadows Global update" desync that the `model` case guards against. const { target } = pickConfigTarget('g1nation', 'knowledgeMix.secondBrainWeight'); await vscode.workspace.getConfiguration('g1nation').update('knowledgeMix.secondBrainWeight', clamped, target); logInfo(`Knowledge Mix (global) updated to: ${clamped}`, { target }); return true; } // โ”€โ”€ Project Architecture (Feature 2) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ case 'getArchitectureStatus': await provider._sendArchitectureStatus(); return true; case 'openArchitectureDoc': await provider._openArchitectureDoc(); return true; case 'refreshArchitecture': await provider._refreshArchitecture(); return true; case 'detachArchitecture': await provider._detachArchitecture(); return true; case 'attachArchitecture': // Re-enable architecture context for the current workspace โ€” // user clicked the inactive chip's [Attach] button. await provider._attachArchitecture(); return true; case 'activateArchitectureFromText': { // Optional explicit-toggle path: webview can pass arbitrary text // (e.g. the current input draft) for one-shot intent detection. if (typeof data.text === 'string') { await provider._tryActivateArchitectureFromText(data.text); } return true; } // โ”€โ”€ 1์ธ ๊ธฐ์—… ๋ชจ๋“œ ๋ฉ”์‹œ์ง€ ๋ผ์šฐํŒ… โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // ๋ณ„๋„ ๋„๋ฉ”์ธ ํ•ธ๋“ค๋Ÿฌ์— ์œ„์ž„. 30+ ๊ฐœ์˜ ํšŒ์‚ฌ ๋ชจ๋“œ ๋ฉ”์‹œ์ง€๊ฐ€ ํ•œ ํŒŒ์ผ๋กœ ๋ถ€ํ’€์–ด // SRP ๊ฐ€ ๊นจ์กŒ๋˜ ๊ฑธ ๋ถ„๋ฆฌ. ์ฒ˜๋ฆฌ ๋ชป ํ•œ ๊ฒฝ์šฐ false ๋ฐ˜ํ™˜ โ†’ ์•„๋ž˜ default ๋กœ ํ๋ฆ„. case 'getCompanyStatus': case 'getCompanyAgents': case 'getCompanyResumable': case 'discardResumableSession': case 'setCompanyEnabled': case 'setCompanyName': case 'setCompanyActiveAgents': case 'setCompanyAgentModel': case 'setCompanyAgentDisplay': case 'setCompanyAgentRoleCategory': case 'setCompanyAgentKnowledgeMix': case 'setCompanyAgentPrompt': case 'addCompanyAgent': case 'deleteCompanyAgent': case 'restoreHiddenAgent': case 'getCompanyPipelines': case 'upsertCompanyPipeline': case 'deleteCompanyPipeline': case 'getCompanyPipelineTemplate': case 'getPixelOfficeState': case 'openPixelOfficePanel': case 'respondCompanyAlignment': case 'respondCompanyApproval': case 'setActiveCompanyPipeline': case 'setCompanyScopePreset': case 'resumeCompanyTurn': { const { handleCompanyMessage } = await import('./companyHandlers'); return await handleCompanyMessage(provider, data); } case 'proactiveTrigger': await provider._handleProactiveSuggestion(data.context); return true; case 'exportResponse': { const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath || ''; const defaultPath = path.join(workspacePath, 'g1_response.md'); const uri = await vscode.window.showSaveDialog({ defaultUri: vscode.Uri.file(defaultPath), filters: { 'Markdown': ['md'] } }); if (uri) { await vscode.workspace.fs.writeFile(uri, Buffer.from(data.text, 'utf8')); vscode.window.showInformationMessage(`โœ… Exported to ${path.basename(uri.fsPath)}`); } return true; } case 'approveAction': await provider._agent.approveTransaction(); return true; case 'rejectAction': await provider._agent.rejectTransaction(); return true; default: return false; } }