chore: bump version to 2.80.27 and update core features
This commit is contained in:
+212
-2
@@ -21,8 +21,22 @@ import { initAstraPathResolver } from './core/astraPath';
|
||||
import { LMStudioClient } from './lmstudio/client';
|
||||
import { ActivityTracker } from './lmstudio/activityTracker';
|
||||
import { ModelLifecycleManager } from './lmstudio/lifecycleManager';
|
||||
import { LMStudioStreamer } from './lmstudio/streamer';
|
||||
import { NodeSystemSpecsProvider, HeuristicModelMemoryEstimator } from './system/specs';
|
||||
import { ApprovalQueue } from './features/approval/approvalQueue';
|
||||
import { ApprovalPanelProvider } from './features/approval/approvalPanelProvider';
|
||||
import { ApprovalStatusBar } from './features/approval/approvalStatusBar';
|
||||
import { FileSystemProjectScaffolder } from './scaffolder/projectScaffolder';
|
||||
import type { ProjectTemplateId } from './scaffolder/templates';
|
||||
import { TelegramHttpClient } from './integrations/telegram/telegramClient';
|
||||
import { TelegramBot } from './integrations/telegram/telegramBot';
|
||||
import { AIService } from './core/services';
|
||||
import { SettingsPanelProvider } from './features/settings/settingsPanelProvider';
|
||||
|
||||
let _lifecycleManager: ModelLifecycleManager | undefined;
|
||||
let _telegramBot: TelegramBot | undefined;
|
||||
|
||||
const TELEGRAM_TOKEN_SECRET_KEY = 'g1nation.telegram.botToken';
|
||||
|
||||
/**
|
||||
* Astra Extension Entry Point
|
||||
@@ -52,6 +66,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const initialUrl = getConfig().ollamaUrl;
|
||||
const activityTracker = new ActivityTracker();
|
||||
const lmStudioClient = new LMStudioClient(initialUrl);
|
||||
const systemSpecs = new NodeSystemSpecsProvider();
|
||||
const memoryEstimator = new HeuristicModelMemoryEstimator();
|
||||
logInfo('System specs detected.', { summary: systemSpecs.get().summary });
|
||||
const lifecycle = new ModelLifecycleManager({
|
||||
client: lmStudioClient,
|
||||
activity: activityTracker,
|
||||
@@ -64,6 +81,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
},
|
||||
notifyError: (msg) => provider?.postLmStudioError(msg),
|
||||
initialEngine: resolveEngine(initialUrl),
|
||||
systemSpecs,
|
||||
memoryEstimator,
|
||||
});
|
||||
_lifecycleManager = lifecycle;
|
||||
context.subscriptions.push({ dispose: () => activityTracker.dispose() });
|
||||
@@ -79,18 +98,48 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
})
|
||||
);
|
||||
|
||||
// 3. Initialize Agent Executor (with stream lifecycle hooks)
|
||||
// Keep the sidebar's model dropdown in sync when defaultModel / ollamaUrl is
|
||||
// changed from elsewhere (Settings panel, raw settings.json, …). Without
|
||||
// this the user sees a desync: Settings shows the new model, sidebar still
|
||||
// shows the old one until a manual refresh.
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeConfiguration((e) => {
|
||||
const touchedModel = e.affectsConfiguration('g1nation.defaultModel');
|
||||
const touchedUrl = e.affectsConfiguration('g1nation.ollamaUrl');
|
||||
if (!touchedModel && !touchedUrl) return;
|
||||
// _sendModels is best-effort; the provider may not have a webview
|
||||
// attached yet during very early activation.
|
||||
void provider?._sendModels(touchedUrl);
|
||||
})
|
||||
);
|
||||
|
||||
// 3. Initialize Approval subsystem (queue + panel webview + status bar badge)
|
||||
const approvalQueue = new ApprovalQueue();
|
||||
const approvalPanel = new ApprovalPanelProvider(context.extensionUri, approvalQueue);
|
||||
const approvalStatusBar = new ApprovalStatusBar(approvalQueue);
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewViewProvider(ApprovalPanelProvider.viewType, approvalPanel),
|
||||
approvalStatusBar,
|
||||
{ dispose: () => approvalQueue.dispose() },
|
||||
vscode.commands.registerCommand(ApprovalStatusBar.focusCommand, () => approvalPanel.focus()),
|
||||
);
|
||||
|
||||
// 4. Initialize Agent Executor (with stream lifecycle hooks + LM Studio SDK streamer + approval queue)
|
||||
const lmStudioStreamer = new LMStudioStreamer(lmStudioClient);
|
||||
const agent = new AgentExecutor(context, {
|
||||
onStreamLifecycle: {
|
||||
start: () => lifecycle.onStreamStart(),
|
||||
end: () => lifecycle.onStreamEnd(),
|
||||
},
|
||||
lmStudioStreamer,
|
||||
approvalQueue,
|
||||
});
|
||||
|
||||
// 4. Initialize Sidebar Provider
|
||||
provider = new SidebarChatProvider(context.extensionUri, context, agent, {
|
||||
lifecycle,
|
||||
activity: activityTracker,
|
||||
loadedModels: () => lmStudioClient.listLoadedCached(),
|
||||
});
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewViewProvider(SidebarChatProvider.viewType, provider)
|
||||
@@ -120,7 +169,164 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('g1nation.syncBrain', async () => {
|
||||
await provider.syncBrain();
|
||||
await provider!.syncBrain();
|
||||
})
|
||||
);
|
||||
|
||||
// Telegram Bot integration — opt-in (g1nation.telegram.enabled), token in SecretStorage.
|
||||
let _cachedTelegramToken = (await context.secrets.get(TELEGRAM_TOKEN_SECRET_KEY)) || '';
|
||||
const telegramClient = new TelegramHttpClient({
|
||||
getToken: () => _cachedTelegramToken,
|
||||
});
|
||||
const telegramAi = new AIService();
|
||||
const telegramBot = new TelegramBot({
|
||||
client: telegramClient,
|
||||
handle: async (text, chatId) => {
|
||||
const cfg = vscode.workspace.getConfiguration('g1nation');
|
||||
const allowed = cfg.get<number[]>('telegram.allowedChatIds', []) || [];
|
||||
if (allowed.length > 0 && !allowed.includes(chatId)) {
|
||||
logInfo('Telegram message from unallowed chat ignored.', { chatId });
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const reply = await telegramAi.call(text);
|
||||
return (reply && reply.trim()) ? reply : '(빈 응답)';
|
||||
} catch (e: any) {
|
||||
return `⚠️ Astra error: ${e?.message ?? e}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
_telegramBot = telegramBot;
|
||||
|
||||
const refreshTelegramBot = async () => {
|
||||
const enabled = vscode.workspace.getConfiguration('g1nation').get<boolean>('telegram.enabled', false);
|
||||
const tokenPresent = !!_cachedTelegramToken.trim();
|
||||
if (enabled && tokenPresent) {
|
||||
telegramBot.start();
|
||||
} else if (telegramBot.isRunning()) {
|
||||
await telegramBot.stop();
|
||||
}
|
||||
};
|
||||
void refreshTelegramBot();
|
||||
|
||||
context.subscriptions.push(
|
||||
{ dispose: () => { void telegramBot.stop(); } },
|
||||
vscode.workspace.onDidChangeConfiguration(async (e) => {
|
||||
if (e.affectsConfiguration('g1nation.telegram.enabled')) {
|
||||
await refreshTelegramBot();
|
||||
}
|
||||
}),
|
||||
context.secrets.onDidChange(async (e) => {
|
||||
if (e.key !== TELEGRAM_TOKEN_SECRET_KEY) return;
|
||||
_cachedTelegramToken = (await context.secrets.get(TELEGRAM_TOKEN_SECRET_KEY)) || '';
|
||||
await refreshTelegramBot();
|
||||
}),
|
||||
vscode.commands.registerCommand('g1nation.telegram.setBotToken', async () => {
|
||||
const token = await vscode.window.showInputBox({
|
||||
prompt: 'Telegram bot token (BotFather에서 발급, 형식: 123456:ABC...)',
|
||||
placeHolder: '123456789:AA...',
|
||||
password: true,
|
||||
ignoreFocusOut: true,
|
||||
validateInput: (v) => /^\d+:[A-Za-z0-9_-]{20,}$/.test((v || '').trim())
|
||||
? null
|
||||
: '형식이 올바르지 않습니다 (숫자ID:문자열).',
|
||||
});
|
||||
if (!token) return;
|
||||
await context.secrets.store(TELEGRAM_TOKEN_SECRET_KEY, token.trim());
|
||||
vscode.window.showInformationMessage(
|
||||
'Telegram bot token이 저장되었습니다. settings에서 g1nation.telegram.enabled = true 로 켜세요.'
|
||||
);
|
||||
}),
|
||||
vscode.commands.registerCommand('g1nation.telegram.clearBotToken', async () => {
|
||||
await context.secrets.delete(TELEGRAM_TOKEN_SECRET_KEY);
|
||||
vscode.window.showInformationMessage('Telegram bot token이 삭제되었습니다.');
|
||||
}),
|
||||
vscode.commands.registerCommand('g1nation.telegram.testConnection', async () => {
|
||||
const token = (await context.secrets.get(TELEGRAM_TOKEN_SECRET_KEY)) || '';
|
||||
if (!token) {
|
||||
vscode.window.showErrorMessage('먼저 "Astra: Set Telegram Bot Token" 명령으로 토큰을 등록하세요.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const me = await telegramClient.getMe();
|
||||
vscode.window.showInformationMessage(
|
||||
`Telegram 연결 성공: @${me.username || me.first_name} (id ${me.id})`
|
||||
);
|
||||
} catch (e: any) {
|
||||
vscode.window.showErrorMessage(`Telegram 연결 실패: ${e?.message ?? e}`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// Astra Settings webview — single entry point for user-facing config (Phase 5-A: Telegram only).
|
||||
const settingsPanel = new SettingsPanelProvider({
|
||||
extensionUri: context.extensionUri,
|
||||
secrets: context.secrets,
|
||||
telegramClient,
|
||||
telegramBot,
|
||||
});
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewViewProvider(SettingsPanelProvider.viewType, settingsPanel),
|
||||
// Refresh the settings UI whenever any g1nation.* config changes (toggle, allowedChatIds, …).
|
||||
vscode.workspace.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration('g1nation')) void settingsPanel.refresh();
|
||||
}),
|
||||
// Same for SecretStorage updates (token saved/cleared from elsewhere).
|
||||
context.secrets.onDidChange((e) => {
|
||||
if (e.key === TELEGRAM_TOKEN_SECRET_KEY) void settingsPanel.refresh();
|
||||
}),
|
||||
vscode.commands.registerCommand('g1nation.settings.focus', () => settingsPanel.focus()),
|
||||
vscode.commands.registerCommand('g1nation.settings.diagnose', async () => {
|
||||
// Diagnostic helper: shows whether the view is registered + opens it if so.
|
||||
// Useful when the user reports "Set 버튼이 안 먹는다" and we want to confirm
|
||||
// the new build is actually loaded.
|
||||
try {
|
||||
await settingsPanel.focus();
|
||||
vscode.window.showInformationMessage('Astra Settings 패널이 열렸습니다. 사이드바 Settings 항목을 확인하세요.');
|
||||
} catch (e: any) {
|
||||
vscode.window.showErrorMessage(`Settings 패널 열기 실패 (확장 reload가 필요할 수 있음): ${e?.message ?? e}`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// Project Scaffolder — Astra의 Developer 빠른 시작 명령
|
||||
const scaffolder = new FileSystemProjectScaffolder();
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('g1nation.scaffoldProject', async () => {
|
||||
const folders = vscode.workspace.workspaceFolders;
|
||||
if (!folders || folders.length === 0) {
|
||||
vscode.window.showErrorMessage('워크스페이스 폴더를 먼저 여세요.');
|
||||
return;
|
||||
}
|
||||
const name = await vscode.window.showInputBox({
|
||||
placeHolder: '프로젝트 이름 (영문/숫자/_/-, 2~40자)',
|
||||
prompt: 'Astra가 워크스페이스 안에 만들 프로젝트 폴더 이름',
|
||||
validateInput: (v) => /^[a-zA-Z0-9_-]{2,40}$/.test(v.trim()) ? null : '영문/숫자/_/- 만, 2~40자',
|
||||
});
|
||||
if (!name) return;
|
||||
const picked = await vscode.window.showQuickPick(
|
||||
scaffolder.listTemplates().map(t => ({ label: t.label, detail: t.detail, id: t.id })),
|
||||
{ placeHolder: '템플릿 선택' }
|
||||
);
|
||||
if (!picked) return;
|
||||
|
||||
const result = await scaffolder.scaffold({
|
||||
name: name.trim(),
|
||||
template: picked.id as ProjectTemplateId,
|
||||
rootDir: folders[0].uri.fsPath,
|
||||
});
|
||||
if (!result.ok) {
|
||||
vscode.window.showErrorMessage(`프로젝트 생성 실패: ${result.error}`);
|
||||
return;
|
||||
}
|
||||
const action = await vscode.window.showInformationMessage(
|
||||
`✅ ${name} 생성 완료 — ${result.projectPath}`,
|
||||
'폴더 열기',
|
||||
'닫기'
|
||||
);
|
||||
if (action === '폴더 열기') {
|
||||
await vscode.commands.executeCommand('revealFileInOS', vscode.Uri.file(result.projectPath));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -133,6 +339,10 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
export async function deactivate() {
|
||||
HealthCheckMonitor.dispose();
|
||||
if (_telegramBot) {
|
||||
try { await _telegramBot.stop(); } catch (e) { logError('Telegram bot stop during deactivate failed.', e); }
|
||||
_telegramBot = undefined;
|
||||
}
|
||||
if (_lifecycleManager) {
|
||||
try {
|
||||
await _lifecycleManager.disposeAndUnload(2000);
|
||||
|
||||
Reference in New Issue
Block a user