release: v2.0.7 - Enhanced Telegram Reporting & File Visibility (2026-05-14)

This commit is contained in:
g1nation
2026-05-14 00:37:41 +09:00
parent f1d5dbf031
commit 8104caf8d9
11 changed files with 187 additions and 20 deletions
+117 -1
View File
@@ -33,6 +33,7 @@ import type { ProjectTemplateId } from './scaffolder/templates';
import { TelegramHttpClient } from './integrations/telegram/telegramClient';
import { TelegramBot } from './integrations/telegram/telegramBot';
import { AIService } from './core/services';
import type { CompanyState } from './features/company';
import { SettingsPanelProvider } from './features/settings/settingsPanelProvider';
import { resolveScopeForAgent, openKnowledgeMapEditor } from './skills/agentKnowledgeMap';
import { getBrainTokenIndex } from './retrieval';
@@ -232,6 +233,83 @@ export async function activate(context: vscode.ExtensionContext) {
};
/** Telegram has a 4096-char per-message limit. Split on paragraph/sentence boundaries to keep replies readable. */
/**
* Cheap heuristic: does the message look like a *work order* the user
* wants the company to execute? Triggers company-turn routing.
*
* Conservative matches only — we'd rather miss a borderline case
* (user retries with clearer wording) than mis-route a question
* into a company turn (which spends LLM calls + writes to disk).
*
* Positive signals:
* • Explicit dispatch prefix: "CEO한테", "회사한테", "팀한테"
* • Korean imperative verbs at sentence end: 만들어/해줘/작성해줘/
* 짜줘/구현해/만들어줘/돌려줘/실행해줘/분석해줘/정리해줘
* • English imperatives: "make X", "build X", "create X", "implement"
*
* Negative signals (override → treat as question, not order):
* • Ends with "?" — pure question
* • Contains "알려줘 / 어디 / 뭐야 / what / where" — informational
*/
function _looksLikeWorkOrder(text: string): boolean {
const t = (text || '').trim();
if (!t) return false;
// Explicit dispatch prefix wins regardless of other signals.
if (/(CEO|회사|팀)\s*(한테|에게|보고|에)/i.test(t)) return true;
// Strong informational signals — *not* a work order.
if (/[?]$/.test(t)) return false;
if (/(어디(에|에서|야)|뭐야|얼마|언제|왜|^(누구|어떻게|뭐))/i.test(t)) return false;
// Korean imperative tails (한국어 청유·명령형 종결).
if (/(만들어|짜줘|작성해|구현해|돌려줘|실행해|분석해|정리해|보고해|해줘|짜봐|만들어줘)/i.test(t)) return true;
// English imperative leads.
if (/^\s*(make|build|create|implement|run|analyze|generate|write|fix|add|remove)\b/i.test(t)) return true;
return false;
}
/**
* Build a `[COMPANY CONTEXT]` block describing the workspace, the
* current company state, and the most recent session directory. Lets
* the bot answer questions like "어디에 저장했어?" by reading its own
* mirror history *plus* the resolved absolute path on disk.
*
* Returns '' when company mode is off, so the prompt stays minimal
* for users who only use the Telegram bot for RAG-chat.
*/
function _buildTelegramCompanyContext(state: CompanyState, ctx: vscode.ExtensionContext): string {
if (!state.enabled) return '';
const ws = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
const lines: string[] = [`[COMPANY CONTEXT]`];
lines.push(`회사명: ${state.companyName || '1인 기업'}`);
if (ws) lines.push(`작업 폴더 (워크스페이스 루트): ${ws}`);
// Surface the most recent session dir so follow-up questions
// ("폴더 어디에 있어?", "방금 만든 파일 경로") have a concrete answer.
const latestSession = _latestCompanySessionDir(ctx);
if (latestSession) {
lines.push(`최근 작업 세션 폴더: ${latestSession}`);
lines.push(`(이 안에 _brief.md, _report.md, 각 에이전트별 산출물이 저장됨)`);
}
lines.push('');
lines.push('당신의 역할: 이 회사의 비서(Secretary). 사용자(사장님)의 질문에 답할 때 위 경로 정보를 *그대로* 활용하세요.');
lines.push('"실제 파일 시스템에 접근할 수 없다" 같은 답변은 잘못된 것입니다 — 위 경로가 실제 시스템 경로입니다.');
return lines.join('\n');
}
/** Find the newest `<workspace>/.astra/company/sessions/<ts>/` directory, or '' if none. */
function _latestCompanySessionDir(ctx: vscode.ExtensionContext): string {
try {
const ws = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
const baseDir = ws
? path.join(ws, '.astra', 'company', 'sessions')
: path.join(ctx.globalStorageUri.fsPath, 'company', 'sessions');
if (!fs.existsSync(baseDir)) return '';
const dirs = fs.readdirSync(baseDir)
.filter((n) => fs.statSync(path.join(baseDir, n)).isDirectory())
.sort()
.reverse();
return dirs[0] ? path.join(baseDir, dirs[0]) : '';
} catch { return ''; }
}
const chunkTelegramMessage = (text: string, max = 4000): string[] => {
if (text.length <= max) return [text];
const out: string[] = [];
@@ -269,6 +347,36 @@ export async function activate(context: vscode.ExtensionContext) {
preview: text.length > 80 ? text.slice(0, 80) + '…' : text,
});
// ── 1인 기업 모드 라우팅 ────────────────────────────────────────
// When company mode is on AND the message looks like a work
// *order* (imperative verbs like "만들어줘 / 해줘 / 작성해줘" or
// an explicit "CEO한테 …" prefix), route through the company
// dispatcher instead of the simple RAG-chat path. The dispatcher
// emits a Telegram mirror at the end, so the user gets a proper
// report back. This fixes the previous symptom where the bot
// refused to "deliver messages to CEO" — that was a routing gap,
// not a missing capability.
const { readCompanyState } = await import('./features/company');
const companyState = readCompanyState(context);
if (companyState.enabled && _looksLikeWorkOrder(text)) {
const { appendTelegramMessage } = await import('./integrations/telegram/conversationHistory');
appendTelegramMessage({ chatId, role: 'user', text, kind: 'user' });
logInfo('Telegram: routing to company turn.', { chatId, preview: text.slice(0, 60) });
// Fire-and-forget: the dispatcher's secretary mirror sends
// the final report. We return an immediate ack so the user
// sees the bot acknowledged the order.
void (async () => {
try {
await provider!._runCompanyTurn(text);
} catch (e: any) {
logError('Telegram → company turn failed.', { error: e?.message ?? String(e) });
}
})();
const ack = '🧭 CEO에게 전달했어요. 작업 끝나면 보고드릴게요.';
appendTelegramMessage({ chatId, role: 'assistant', text: ack, kind: 'reply' });
return ack;
}
// Per-chat agent override → global default → mapping default.
const perChatAgents = cfg.get<Record<string, string>>('telegram.agentByChatId', {}) || {};
const perChatAgent = perChatAgents[String(chatId)];
@@ -305,7 +413,15 @@ export async function activate(context: vscode.ExtensionContext) {
}
}
const systemPrompt = buildTelegramSystemPrompt(!!contextBlock);
// Build the system prompt with company-aware context. The bot
// is the *secretary* of a virtual company when company mode is
// on — telling it so lets it answer "where did you save X?"
// properly instead of falling back to "I don't have file
// system access".
const companyContextBlock = _buildTelegramCompanyContext(companyState, context);
const systemPrompt = buildTelegramSystemPrompt(!!contextBlock)
+ (companyContextBlock ? `\n\n${companyContextBlock}` : '');
// Per-chat conversation history — without this every inbound
// is a fresh turn, so the user "tells the bot something" and
// it gets immediately forgotten. We inline the recent N