release: v2.0.7 - Enhanced Telegram Reporting & File Visibility (2026-05-14)
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
|
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
|
||||||
"createdAt": 1778685193682,
|
"createdAt": 1778686589725,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
||||||
"createdAt": 1778685193681,
|
"createdAt": 1778686589724,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
"result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
||||||
"createdAt": 1778685193680,
|
"createdAt": 1778686589722,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+2
-2
@@ -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",
|
"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": 1778685193682,
|
"createdAt": 1778686589726,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+10
-10
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"missionId": "stress_conflict_1778685193665",
|
"missionId": "stress_conflict_1778686589692",
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"startTime": "2026-05-13T15:13:13.666Z",
|
"startTime": "2026-05-13T15:36:29.692Z",
|
||||||
"totalElapsedMs": 16,
|
"totalElapsedMs": 34,
|
||||||
"results": {
|
"results": {
|
||||||
"planner": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
"planner": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
||||||
"researcher": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
"researcher": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
||||||
@@ -16,30 +16,30 @@
|
|||||||
{
|
{
|
||||||
"from": "idle",
|
"from": "idle",
|
||||||
"to": "planner",
|
"to": "planner",
|
||||||
"durationMs": 10,
|
"durationMs": 20,
|
||||||
"message": "전략 수립 중...",
|
"message": "전략 수립 중...",
|
||||||
"ts": "2026-05-13T15:13:13.676Z"
|
"ts": "2026-05-13T15:36:29.712Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "planner",
|
"from": "planner",
|
||||||
"to": "researcher",
|
"to": "researcher",
|
||||||
"durationMs": 5,
|
"durationMs": 11,
|
||||||
"message": "핵심 정보 수집 및 분석 중...",
|
"message": "핵심 정보 수집 및 분석 중...",
|
||||||
"ts": "2026-05-13T15:13:13.681Z"
|
"ts": "2026-05-13T15:36:29.723Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "researcher",
|
"from": "researcher",
|
||||||
"to": "writer",
|
"to": "writer",
|
||||||
"durationMs": 0,
|
"durationMs": 2,
|
||||||
"message": "최종 리포트 작성 및 편집 중...",
|
"message": "최종 리포트 작성 및 편집 중...",
|
||||||
"ts": "2026-05-13T15:13:13.681Z"
|
"ts": "2026-05-13T15:36:29.725Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "writer",
|
"from": "writer",
|
||||||
"to": "completed",
|
"to": "completed",
|
||||||
"durationMs": 1,
|
"durationMs": 1,
|
||||||
"message": "미션 완료",
|
"message": "미션 완료",
|
||||||
"ts": "2026-05-13T15:13:13.682Z"
|
"ts": "2026-05-13T15:36:29.726Z"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"resilienceMetrics": {
|
"resilienceMetrics": {
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
# Astra Patch Notes
|
# 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)
|
## v2.0.6 (2026-05-14)
|
||||||
### 🚀 Intelligence & UX Optimization
|
### 🚀 Intelligence & UX Optimization
|
||||||
- **UI & UX 정교화:** 사이드바(`sidebar.js`, `sidebar.css`)의 인터랙션과 스타일을 개선하여 더욱 매끄러운 사용자 경험을 제공합니다.
|
- **UI & UX 정교화:** 사이드바(`sidebar.js`, `sidebar.css`)의 인터랙션과 스타일을 개선하여 더욱 매끄러운 사용자 경험을 제공합니다.
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "astra",
|
"name": "astra",
|
||||||
"displayName": "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.",
|
"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",
|
"publisher": "g1nation",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"icon": "assets/icon.png",
|
"icon": "assets/icon.png",
|
||||||
|
|||||||
+117
-1
@@ -33,6 +33,7 @@ import type { ProjectTemplateId } from './scaffolder/templates';
|
|||||||
import { TelegramHttpClient } from './integrations/telegram/telegramClient';
|
import { TelegramHttpClient } from './integrations/telegram/telegramClient';
|
||||||
import { TelegramBot } from './integrations/telegram/telegramBot';
|
import { TelegramBot } from './integrations/telegram/telegramBot';
|
||||||
import { AIService } from './core/services';
|
import { AIService } from './core/services';
|
||||||
|
import type { CompanyState } from './features/company';
|
||||||
import { SettingsPanelProvider } from './features/settings/settingsPanelProvider';
|
import { SettingsPanelProvider } from './features/settings/settingsPanelProvider';
|
||||||
import { resolveScopeForAgent, openKnowledgeMapEditor } from './skills/agentKnowledgeMap';
|
import { resolveScopeForAgent, openKnowledgeMapEditor } from './skills/agentKnowledgeMap';
|
||||||
import { getBrainTokenIndex } from './retrieval';
|
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. */
|
/** 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[] => {
|
const chunkTelegramMessage = (text: string, max = 4000): string[] => {
|
||||||
if (text.length <= max) return [text];
|
if (text.length <= max) return [text];
|
||||||
const out: string[] = [];
|
const out: string[] = [];
|
||||||
@@ -269,6 +347,36 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
preview: text.length > 80 ? text.slice(0, 80) + '…' : text,
|
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.
|
// Per-chat agent override → global default → mapping default.
|
||||||
const perChatAgents = cfg.get<Record<string, string>>('telegram.agentByChatId', {}) || {};
|
const perChatAgents = cfg.get<Record<string, string>>('telegram.agentByChatId', {}) || {};
|
||||||
const perChatAgent = perChatAgents[String(chatId)];
|
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
|
// Per-chat conversation history — without this every inbound
|
||||||
// is a fresh turn, so the user "tells the bot something" and
|
// is a fresh turn, so the user "tells the bot something" and
|
||||||
// it gets immediately forgotten. We inline the recent N
|
// it gets immediately forgotten. We inline the recent N
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ export async function runCompanyTurn(
|
|||||||
outputs,
|
outputs,
|
||||||
report: reportResult.report,
|
report: reportResult.report,
|
||||||
sessionTimestamp: timestamp,
|
sessionTimestamp: timestamp,
|
||||||
|
sessionDir,
|
||||||
});
|
});
|
||||||
telegramOk = await reporter(tgText);
|
telegramOk = await reporter(tgText);
|
||||||
if (!telegramOk) telegramReason = 'delivery-failed';
|
if (!telegramOk) telegramReason = 'delivery-failed';
|
||||||
@@ -284,10 +285,12 @@ async function _dispatchOne(
|
|||||||
// hit disk / shell. The report (e.g. "✅ Created: foo.py") is
|
// hit disk / shell. The report (e.g. "✅ Created: foo.py") is
|
||||||
// appended to the response so the user sees what really happened.
|
// appended to the response so the user sees what really happened.
|
||||||
let finalResponse = rawResponse || '_(empty response)_';
|
let finalResponse = rawResponse || '_(empty response)_';
|
||||||
|
let actionReport: string[] | undefined;
|
||||||
const hasTag = !!rawResponse && _hasActionTag(rawResponse);
|
const hasTag = !!rawResponse && _hasActionTag(rawResponse);
|
||||||
if (rawResponse && deps.executeActionTags && hasTag) {
|
if (rawResponse && deps.executeActionTags && hasTag) {
|
||||||
try {
|
try {
|
||||||
const report = await deps.executeActionTags(rawResponse);
|
const report = await deps.executeActionTags(rawResponse);
|
||||||
|
actionReport = report;
|
||||||
if (report.length > 0) {
|
if (report.length > 0) {
|
||||||
finalResponse = `${rawResponse}\n\n---\n**Action 실행 결과:**\n${report.map((r) => `- ${r}`).join('\n')}`;
|
finalResponse = `${rawResponse}\n\n---\n**Action 실행 결과:**\n${report.map((r) => `- ${r}`).join('\n')}`;
|
||||||
}
|
}
|
||||||
@@ -322,6 +325,7 @@ async function _dispatchOne(
|
|||||||
error: rawResponse
|
error: rawResponse
|
||||||
? (claimedButDidnt ? 'claimed-creation-no-tag' : undefined)
|
? (claimedButDidnt ? 'claimed-creation-no-tag' : undefined)
|
||||||
: 'empty-response',
|
: 'empty-response',
|
||||||
|
actionReport,
|
||||||
};
|
};
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
const err = e?.message ?? String(e);
|
const err = e?.message ?? String(e);
|
||||||
|
|||||||
@@ -105,7 +105,10 @@ export async function buildTelegramReporter(
|
|||||||
/**
|
/**
|
||||||
* Compose the per-turn Telegram message. Format mirrors Connect_origin's
|
* Compose the per-turn Telegram message. Format mirrors Connect_origin's
|
||||||
* Secretary report so users moving between the two products see consistent
|
* 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-
|
* The function is pure (no I/O) and exported separately so it can be unit-
|
||||||
* tested without touching VS Code APIs.
|
* tested without touching VS Code APIs.
|
||||||
@@ -117,6 +120,9 @@ export function formatCompanyTelegramReport(opts: {
|
|||||||
outputs: AgentTurnOutput[];
|
outputs: AgentTurnOutput[];
|
||||||
report: string;
|
report: string;
|
||||||
sessionTimestamp: 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 {
|
}): string {
|
||||||
const company = opts.state.companyName || '1인 기업';
|
const company = opts.state.companyName || '1인 기업';
|
||||||
const header = `*📱 ${company} — 작업 라운드 보고*`;
|
const header = `*📱 ${company} — 작업 라운드 보고*`;
|
||||||
@@ -130,9 +136,33 @@ export function formatCompanyTelegramReport(opts: {
|
|||||||
return `• ${mark} ${def?.emoji ?? ''} ${def?.name ?? t.agent}`;
|
return `• ${mark} ${def?.emoji ?? ''} ${def?.name ?? t.agent}`;
|
||||||
}).join('\n')
|
}).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()
|
const reportBody = opts.report.trim()
|
||||||
? `\n\n${opts.report.trim().slice(0, REPORT_BUDGET)}`
|
? `\n\n${opts.report.trim().slice(0, REPORT_BUDGET)}`
|
||||||
: '';
|
: '';
|
||||||
const sessionLine = `\n\n_세션: ${opts.sessionTimestamp}_`;
|
const sessionLine = `\n\n*세션 폴더:* \`${opts.sessionDir}\``;
|
||||||
return `${header}\n\n${cmdLine}${brief}${agentsLine}${reportBody}${sessionLine}`;
|
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');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,13 @@ export interface AgentTurnOutput {
|
|||||||
durationMs: number;
|
durationMs: number;
|
||||||
/** Populated when the dispatch failed; `response` then holds the error. */
|
/** Populated when the dispatch failed; `response` then holds the error. */
|
||||||
error?: string;
|
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>/. */
|
/** The whole result of a company turn — persisted under sessions/<timestamp>/. */
|
||||||
|
|||||||
Reference in New Issue
Block a user