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
@@ -1,5 +1,5 @@
{
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
"createdAt": 1778685193682,
"createdAt": 1778686589725,
"modelVersion": "unknown"
}
@@ -1,5 +1,5 @@
{
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
"createdAt": 1778685193681,
"createdAt": 1778686589724,
"modelVersion": "unknown"
}
@@ -1,5 +1,5 @@
{
"result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
"createdAt": 1778685193680,
"createdAt": 1778686589722,
"modelVersion": "unknown"
}
@@ -1,5 +1,5 @@
{
"result": "---\nid: stress_conflict_1778685193665\ndate: 2026-05-13T15:13:13.682Z\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]** 전략 수립 중... (10ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (5ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (0ms)\n",
"createdAt": 1778685193682,
"result": "---\nid: stress_conflict_1778686589692\ndate: 2026-05-13T15:36:29.726Z\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]** 전략 수립 중... (20ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (11ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (2ms)\n",
"createdAt": 1778686589726,
"modelVersion": "unknown"
}
@@ -1,8 +1,8 @@
{
"missionId": "stress_conflict_1778685193665",
"missionId": "stress_conflict_1778686589692",
"status": "completed",
"startTime": "2026-05-13T15:13:13.666Z",
"totalElapsedMs": 16,
"startTime": "2026-05-13T15:36:29.692Z",
"totalElapsedMs": 34,
"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": 10,
"durationMs": 20,
"message": "전략 수립 중...",
"ts": "2026-05-13T15:13:13.676Z"
"ts": "2026-05-13T15:36:29.712Z"
},
{
"from": "planner",
"to": "researcher",
"durationMs": 5,
"durationMs": 11,
"message": "핵심 정보 수집 및 분석 중...",
"ts": "2026-05-13T15:13:13.681Z"
"ts": "2026-05-13T15:36:29.723Z"
},
{
"from": "researcher",
"to": "writer",
"durationMs": 0,
"durationMs": 2,
"message": "최종 리포트 작성 및 편집 중...",
"ts": "2026-05-13T15:13:13.681Z"
"ts": "2026-05-13T15:36:29.725Z"
},
{
"from": "writer",
"to": "completed",
"durationMs": 1,
"message": "미션 완료",
"ts": "2026-05-13T15:13:13.682Z"
"ts": "2026-05-13T15:36:29.726Z"
}
],
"resilienceMetrics": {
+10
View File
@@ -1,5 +1,15 @@
# Astra Patch Notes
## v2.0.7 (2026-05-14)
### 📢 Enhanced Telegram Reporting & File Visibility
- **텔레그램 결과물 추적 강화:** 텔레그램 보고서에 에이전트가 생성한 파일 경로(`*결과물:*`)와 세션 폴더 위치를 명시적으로 포함하여, 생성된 자산을 즉시 확인할 수 있도록 개선했습니다.
- **액션 리포팅 시스템 고도화:** `AgentTurnOutput``actionReport` 필드를 추가하여 실행된 액션 태그의 결과를 체계적으로 추적하고 다른 서비스에서 활용할 수 있도록 정교화했습니다.
- **디스패처 안정성 향상:** `dispatcher.ts` 내의 액션 실행 로직을 개선하여 태그 실행 결과와 응답 본문의 정합성을 확보했습니다.
- **타입 안정성 보강:** 비즈니스 엔진 전반의 인터페이스와 타입을 보강하여 유지보수성과 확장성을 높였습니다.
- **신규 패키징:** `astra-2.0.7.vsix` 패키지를 통해 강화된 텔레그램 리포팅과 안정화된 엔진을 배포합니다.
---
## v2.0.6 (2026-05-14)
### 🚀 Intelligence & UX Optimization
- **UI & UX 정교화:** 사이드바(`sidebar.js`, `sidebar.css`)의 인터랙션과 스타일을 개선하여 더욱 매끄러운 사용자 경험을 제공합니다.
+1 -1
View File
@@ -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.0.6",
"version": "2.0.7",
"publisher": "g1nation",
"license": "MIT",
"icon": "assets/icon.png",
+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
+4
View File
@@ -195,6 +195,7 @@ export async function runCompanyTurn(
outputs,
report: reportResult.report,
sessionTimestamp: timestamp,
sessionDir,
});
telegramOk = await reporter(tgText);
if (!telegramOk) telegramReason = 'delivery-failed';
@@ -284,10 +285,12 @@ async function _dispatchOne(
// hit disk / shell. The report (e.g. "✅ Created: foo.py") is
// appended to the response so the user sees what really happened.
let finalResponse = rawResponse || '_(empty response)_';
let actionReport: string[] | undefined;
const hasTag = !!rawResponse && _hasActionTag(rawResponse);
if (rawResponse && deps.executeActionTags && hasTag) {
try {
const report = await deps.executeActionTags(rawResponse);
actionReport = report;
if (report.length > 0) {
finalResponse = `${rawResponse}\n\n---\n**Action 실행 결과:**\n${report.map((r) => `- ${r}`).join('\n')}`;
}
@@ -322,6 +325,7 @@ async function _dispatchOne(
error: rawResponse
? (claimedButDidnt ? 'claimed-creation-no-tag' : undefined)
: 'empty-response',
actionReport,
};
} catch (e: any) {
const err = e?.message ?? String(e);
+33 -3
View File
@@ -105,7 +105,10 @@ export async function buildTelegramReporter(
/**
* Compose the per-turn Telegram message. Format mirrors Connect_origin's
* Secretary report so users moving between the two products see consistent
* notifications: header + brief + agent list + CEO synthesis + session id.
* notifications: header + brief + agent list + CEO synthesis + session id +
* **concrete file paths** so follow-up questions like "어디에 만들었어?"
* have a clean answer (the bot's previous answer "I don't have file system
* access" was the user-visible bug we're fixing here).
*
* The function is pure (no I/O) and exported separately so it can be unit-
* tested without touching VS Code APIs.
@@ -117,6 +120,9 @@ export function formatCompanyTelegramReport(opts: {
outputs: AgentTurnOutput[];
report: string;
sessionTimestamp: string;
/** Absolute path of the session directory — surfaced in the report so
* the user can locate generated files without asking the bot again. */
sessionDir: string;
}): string {
const company = opts.state.companyName || '1인 기업';
const header = `*📱 ${company} — 작업 라운드 보고*`;
@@ -130,9 +136,33 @@ export function formatCompanyTelegramReport(opts: {
return `${mark} ${def?.emoji ?? ''} ${def?.name ?? t.agent}`;
}).join('\n')
: '';
// Concrete artefacts: extract the file paths the action-tag executor
// actually wrote, so the user can copy-paste them into Finder or `cd` to
// the right place. Empty when no agent emitted file/command tags.
const artefactsLine = _formatArtefactsBlock(opts.outputs);
const reportBody = opts.report.trim()
? `\n\n${opts.report.trim().slice(0, REPORT_BUDGET)}`
: '';
const sessionLine = `\n\n_세션: ${opts.sessionTimestamp}_`;
return `${header}\n\n${cmdLine}${brief}${agentsLine}${reportBody}${sessionLine}`;
const sessionLine = `\n\n*세션 폴더:* \`${opts.sessionDir}\``;
return `${header}\n\n${cmdLine}${brief}${agentsLine}${artefactsLine}${reportBody}${sessionLine}`;
}
/**
* Pull a `*결과물:*` block out of the per-agent action reports. The
* executor emits lines like `"✅ Created: testtimer/timer.py"` —
* we re-format them with the workspace-relative path intact so the user
* can `cd <workspace>/testtimer` and find the file.
*/
function _formatArtefactsBlock(outputs: AgentTurnOutput[]): string {
const lines: string[] = [];
for (const out of outputs) {
if (!out.actionReport || out.actionReport.length === 0) continue;
for (const r of out.actionReport) {
// Keep success lines verbatim; trim long shell-output blocks.
const trimmed = r.length > 200 ? r.slice(0, 200) + '…' : r;
lines.push(`${trimmed}`);
}
}
if (lines.length === 0) return '';
return '\n\n*결과물:*\n' + lines.join('\n');
}
+7
View File
@@ -101,6 +101,13 @@ export interface AgentTurnOutput {
durationMs: number;
/** Populated when the dispatch failed; `response` then holds the error. */
error?: string;
/**
* Lines returned by the action-tag executor — e.g. `"✅ Created: testtimer/timer.py"`.
* Kept separate from `response` so downstream surfaces (Telegram mirror,
* sidebar chip) can extract concrete file paths instead of regex-scraping
* the prose. Empty / absent when the agent emitted no action tags.
*/
actionReport?: string[];
}
/** The whole result of a company turn — persisted under sessions/<timestamp>/. */