From 39386f90b54541dcde1795cb00350c8a262349dd Mon Sep 17 00:00:00 2001 From: g1nation Date: Wed, 13 May 2026 23:54:34 +0900 Subject: [PATCH] release: v2.0.5 - Telegram Business Reporting & Core Resilience --- ...d46d2ca2057b05c488be1dcf439166ac5a9a1.json | 2 +- ...9f4f39d2bc368f77456c37b5eef9a94a66b5c.json | 2 +- ...5c7a44d7661af673b24e3f49551a7a2e50280.json | 2 +- ...adc543795e4b427b64540a49c9ab27c7fe213.json | 4 +- ...son => stress_conflict_1778684049621.json} | 20 +-- PATCHNOTES.md | 9 ++ media/sidebar.js | 13 ++ package.json | 2 +- src/agent.ts | 29 ++++ src/features/company/dispatcher.ts | 83 ++++++++++- src/features/company/telegramReport.ts | 130 ++++++++++++++++++ src/sidebarProvider.ts | 12 ++ 12 files changed, 288 insertions(+), 20 deletions(-) rename .astra/tests/stress/.astra/missions/{stress_conflict_1778682718185.json => stress_conflict_1778684049621.json} (80%) create mode 100644 src/features/company/telegramReport.ts diff --git a/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json b/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json index e971f8e..f3a270f 100644 --- a/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json +++ b/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json @@ -1,5 +1,5 @@ { "result": "Final report with inconsistencies. This should be long enough to pass validation.", - "createdAt": 1778682718199, + "createdAt": 1778684049645, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/cache/65775be352df43297b63c7af59c9f4f39d2bc368f77456c37b5eef9a94a66b5c.json b/.astra/tests/stress/.astra/cache/65775be352df43297b63c7af59c9f4f39d2bc368f77456c37b5eef9a94a66b5c.json index 89fce0f..8f780e6 100644 --- a/.astra/tests/stress/.astra/cache/65775be352df43297b63c7af59c9f4f39d2bc368f77456c37b5eef9a94a66b5c.json +++ b/.astra/tests/stress/.astra/cache/65775be352df43297b63c7af59c9f4f39d2bc368f77456c37b5eef9a94a66b5c.json @@ -1,5 +1,5 @@ { "result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.", - "createdAt": 1778682718198, + "createdAt": 1778684049641, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/cache/6894d26c5b0a55d25d756a473225c7a44d7661af673b24e3f49551a7a2e50280.json b/.astra/tests/stress/.astra/cache/6894d26c5b0a55d25d756a473225c7a44d7661af673b24e3f49551a7a2e50280.json index 242d4c8..2a4c637 100644 --- a/.astra/tests/stress/.astra/cache/6894d26c5b0a55d25d756a473225c7a44d7661af673b24e3f49551a7a2e50280.json +++ b/.astra/tests/stress/.astra/cache/6894d26c5b0a55d25d756a473225c7a44d7661af673b24e3f49551a7a2e50280.json @@ -1,5 +1,5 @@ { "result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.", - "createdAt": 1778682718197, + "createdAt": 1778684049636, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/cache/88cb61499f88ed38165b64bd3e8adc543795e4b427b64540a49c9ab27c7fe213.json b/.astra/tests/stress/.astra/cache/88cb61499f88ed38165b64bd3e8adc543795e4b427b64540a49c9ab27c7fe213.json index 4b1da63..ed5eb8c 100644 --- a/.astra/tests/stress/.astra/cache/88cb61499f88ed38165b64bd3e8adc543795e4b427b64540a49c9ab27c7fe213.json +++ b/.astra/tests/stress/.astra/cache/88cb61499f88ed38165b64bd3e8adc543795e4b427b64540a49c9ab27c7fe213.json @@ -1,5 +1,5 @@ { - "result": "---\nid: stress_conflict_1778682718185\ndate: 2026-05-13T14:31:58.199Z\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]** 전략 수립 중... (11ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (1ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (2ms)\n", - "createdAt": 1778682718199, + "result": "---\nid: stress_conflict_1778684049621\ndate: 2026-05-13T14:54:09.649Z\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]** 전략 수립 중... (11ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (5ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (8ms)\n", + "createdAt": 1778684049649, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/missions/stress_conflict_1778682718185.json b/.astra/tests/stress/.astra/missions/stress_conflict_1778684049621.json similarity index 80% rename from .astra/tests/stress/.astra/missions/stress_conflict_1778682718185.json rename to .astra/tests/stress/.astra/missions/stress_conflict_1778684049621.json index 0558b24..bdcea08 100644 --- a/.astra/tests/stress/.astra/missions/stress_conflict_1778682718185.json +++ b/.astra/tests/stress/.astra/missions/stress_conflict_1778684049621.json @@ -1,8 +1,8 @@ { - "missionId": "stress_conflict_1778682718185", + "missionId": "stress_conflict_1778684049621", "status": "completed", - "startTime": "2026-05-13T14:31:58.185Z", - "totalElapsedMs": 14, + "startTime": "2026-05-13T14:54:09.621Z", + "totalElapsedMs": 29, "results": { "planner": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.", "researcher": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.", @@ -18,28 +18,28 @@ "to": "planner", "durationMs": 11, "message": "전략 수립 중...", - "ts": "2026-05-13T14:31:58.196Z" + "ts": "2026-05-13T14:54:09.632Z" }, { "from": "planner", "to": "researcher", - "durationMs": 1, + "durationMs": 5, "message": "핵심 정보 수집 및 분석 중...", - "ts": "2026-05-13T14:31:58.197Z" + "ts": "2026-05-13T14:54:09.637Z" }, { "from": "researcher", "to": "writer", - "durationMs": 2, + "durationMs": 8, "message": "최종 리포트 작성 및 편집 중...", - "ts": "2026-05-13T14:31:58.199Z" + "ts": "2026-05-13T14:54:09.645Z" }, { "from": "writer", "to": "completed", - "durationMs": 0, + "durationMs": 5, "message": "미션 완료", - "ts": "2026-05-13T14:31:58.199Z" + "ts": "2026-05-13T14:54:09.650Z" } ], "resilienceMetrics": { diff --git a/PATCHNOTES.md b/PATCHNOTES.md index 1002ca6..1e4d1c9 100644 --- a/PATCHNOTES.md +++ b/PATCHNOTES.md @@ -1,5 +1,14 @@ # Astra Patch Notes +## v2.0.5 (2026-05-13) +### 📢 Telegram Business Reporting & Core Resilience +- **텔레그램 비즈니스 리포팅 도입:** 비즈니스 에이전트의 성과를 실시간으로 보고하는 `telegramReport.ts`를 추가하여 원격 모니터링 기능을 강화했습니다. +- **에이전트 엔진 복원력 강화:** `agent.ts` 및 `dispatcher.ts` 내의 예외 처리 로직을 보강하여 복잡한 자율 미션 수행 시의 안정성을 높였습니다. +- **사이드바 상태 관리 최적화:** `sidebarProvider.ts`와 `sidebar.js`를 수정하여 세션 전환 및 대화 초기화 시의 반응성을 개선했습니다. +- **신규 패키징:** `astra-2.0.5.vsix` 패키지를 통해 텔레그램 연동 보고와 강화된 엔진 안정성을 통합 배포합니다. + +--- + ## v2.0.4 (2026-05-13) ### ⚡ Advanced Business Orchestration & UI Polishing - **비즈니스 에이전트 고도화:** `companyConfig.ts` 및 `promptBuilder.ts` 수정을 통해 CEO 에이전트의 목표 설정 및 컨텍스트 주입 로직을 정교화했습니다. diff --git a/media/sidebar.js b/media/sidebar.js index 7fe2007..d9b3bf6 100644 --- a/media/sidebar.js +++ b/media/sidebar.js @@ -1693,6 +1693,19 @@ card.className += ' report'; card.innerHTML = `
🧭 CEO 보고서${ev.ok ? '' : ' (fallback)'}
${fmt(ev.report || '')}
`; + } else if (ev.phase === 'telegram-mirror') { + // Reflect whether the secretary actually mirrored the round + // to Telegram. `ok === null` = the user hasn't opted in to + // Telegram at all (no token / chat id / enabled). We render + // that as a quiet line instead of an error to avoid nagging. + if (ev.ok === true) { + card.innerHTML = '
📱 영숙이 텔레그램에 보고 완료
'; + } else if (ev.ok === false) { + card.innerHTML = `
⚠️ 텔레그램 보고 실패${ev.reason ? ` — ${escAttr(ev.reason)}` : ''}
`; + } else { + // null → not configured. Skip rendering entirely to keep chat clean. + return; + } } else if (ev.phase === 'session-saved') { card.innerHTML = `
세션 저장 완료 — 클릭하여 열기
`; card.style.cursor = 'pointer'; diff --git a/package.json b/package.json index 1ccf2db..453889f 100644 --- a/package.json +++ b/package.json @@ -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.4", + "version": "2.0.5", "publisher": "g1nation", "license": "MIT", "icon": "assets/icon.png", diff --git a/src/agent.ts b/src/agent.ts index 0be0e3d..06c1fe2 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -3054,6 +3054,35 @@ export class AgentExecutor { return candidates; } + /** + * Public entry point for callers that need to apply ConnectAI's action + * tags (``, ``, ``, …) to arbitrary + * text without going through the full `handlePrompt` pipeline. + * + * The 1인 기업 dispatcher uses this so specialist outputs that contain + * action tags actually take effect on disk — without it, agents would + * "claim" to create files but nothing would be written, which is the + * exact symptom the user reported. + * + * Returns the action report (`["✅ Created: …", "📂 Listed: …", …]`) so + * the caller can surface it back to the user. Errors inside individual + * actions are converted into report entries rather than thrown, matching + * the behaviour of the internal call site. + */ + public async executeActionTagsOnText(aiMessage: string): Promise { + const cfg = getConfig(); + const rootPath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath + || cfg.localBrainPath + || process.cwd(); + const activeBrain = getActiveBrainProfile(); + try { + return await this.executeActions(aiMessage, rootPath, activeBrain); + } catch (e: any) { + logError('executeActionTagsOnText failed.', { error: e?.message ?? String(e) }); + return [`❌ Action 실행 중 오류: ${e?.message ?? e}`]; + } + } + private async executeActions(aiMessage: string, rootPath: string, activeBrain: BrainProfile): Promise { const report: string[] = []; let brainModified = false; diff --git a/src/features/company/dispatcher.ts b/src/features/company/dispatcher.ts index 068171b..2b0e6db 100644 --- a/src/features/company/dispatcher.ts +++ b/src/features/company/dispatcher.ts @@ -50,7 +50,8 @@ import { writeReport, writeSessionJson, } from './sessionStore'; -import { AgentTurnOutput, CompanyTaskPlan, SessionResult } from './types'; +import { buildTelegramReporter, formatCompanyTelegramReport } from './telegramReport'; +import { AgentTurnOutput, CompanyState, CompanyTaskPlan, SessionResult } from './types'; /** Trim length applied when an agent's output is fed into the next agent. */ const PEER_OUTPUT_BUDGET = 1500; @@ -68,6 +69,13 @@ export type CompanyTurnEvent = | { phase: 'agent-done'; agentId: string; output: AgentTurnOutput; index: number; total: number } | { phase: 'report-start' } | { phase: 'report-done'; report: string; ok: boolean } + /** + * Emitted after the secretary attempts to mirror the CEO report to + * Telegram. `ok=true` ⇒ delivered. `ok=false` ⇒ delivery failed (network + * / API error). `ok=null` ⇒ no mirror was attempted because the user + * hasn't opted in (no token, no chat id, or `telegram.enabled=false`). + */ + | { phase: 'telegram-mirror'; ok: boolean | null; reason?: string } | { phase: 'session-saved'; sessionDir: string } | { phase: 'aborted'; reason: string }; @@ -78,6 +86,15 @@ export interface DispatcherDeps { ai: IAIService; /** Default model to fall back to when an agent has no override. */ defaultModel: string; + /** + * Apply ConnectAI's action-tag executor to the specialist's raw response. + * Without this hook, agent outputs containing `` etc. would + * be shown to the user as a *claim* of file creation but nothing would + * actually land on disk. We thread it through deps (rather than + * importing AgentExecutor directly) so the dispatcher stays free of + * sidebar / agent.ts dependencies for unit testability. + */ + executeActionTags?: (text: string) => Promise; /** Per-call cancellation. The sidebar's Stop button flips this. */ signal?: AbortSignal; /** Optional event sink for the webview. Receives events synchronously. */ @@ -160,6 +177,35 @@ export async function runCompanyTurn( writeReport(sessionDir, reportResult.report); emit({ phase: 'report-done', report: reportResult.report, ok: reportResult.ok }); + // ── Phase 3.5: Secretary mirror to Telegram ── + // Origin parity (Connect_origin extension.ts ~line 20620): after the CEO + // synthesizes the round, the Secretary (영숙) ships a digest to the user's + // Telegram chat. Strictly best-effort — failures never break the turn. + let telegramOk: boolean | null = null; + let telegramReason: string | undefined; + try { + const reporter = await buildTelegramReporter(deps.context); + if (!reporter) { + telegramReason = 'no-config'; // opt-in off, no token, or no chat id + } else { + const tgText = formatCompanyTelegramReport({ + state, + userPrompt, + plan: plannerResult.plan, + outputs, + report: reportResult.report, + sessionTimestamp: timestamp, + }); + telegramOk = await reporter(tgText); + if (!telegramOk) telegramReason = 'delivery-failed'; + } + } catch (e: any) { + telegramOk = false; + telegramReason = e?.message ?? String(e); + logError('company.dispatcher: telegram mirror threw.', { error: telegramReason }); + } + emit({ phase: 'telegram-mirror', ok: telegramOk, reason: telegramReason }); + // ── Phase 4: persist + side effects ── const result: SessionResult = { timestamp, sessionDir, @@ -232,12 +278,31 @@ async function _dispatchOne( user: task, model, }); - const response = (result.content || '').trim(); + const rawResponse = (result.content || '').trim(); + // Apply ConnectAI's action-tag executor so ``, + // ``, ``, etc. emitted by the agent actually + // 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)_'; + if (rawResponse && deps.executeActionTags && _hasActionTag(rawResponse)) { + try { + const report = await deps.executeActionTags(rawResponse); + if (report.length > 0) { + finalResponse = `${rawResponse}\n\n---\n**Action 실행 결과:**\n${report.map((r) => `- ${r}`).join('\n')}`; + } + } catch (e: any) { + // Surface the failure but keep the agent's text — partial + // success is more useful than dropping the whole response. + const err = e?.message ?? String(e); + logError('company.dispatcher: action-tag execution failed.', { agentId, err }); + finalResponse = `${rawResponse}\n\n---\n⚠️ Action 실행 실패: ${err}`; + } + } return { agentId, task, - response: response || '_(empty response)_', + response: finalResponse, durationMs: Date.now() - startedAt, - error: response ? undefined : 'empty-response', + error: rawResponse ? undefined : 'empty-response', }; } catch (e: any) { const err = e?.message ?? String(e); @@ -250,3 +315,13 @@ async function _dispatchOne( }; } } + +/** + * Cheap pre-check so we don't fire up the action-tag executor for every + * specialist response — only the ones that actually contain a recognised + * tag. Saves a workspace lookup + transaction-manager spin-up on the common + * case (the agent just talks). + */ +function _hasActionTag(text: string): boolean { + return /<\s*(?:create_file|edit_file|delete_file|read_file|list_files|list_brain|run_command|read_brain|reveal_in_explorer|open_file|glob|grep)\b/i.test(text); +} diff --git a/src/features/company/telegramReport.ts b/src/features/company/telegramReport.ts new file mode 100644 index 0000000..47bf4da --- /dev/null +++ b/src/features/company/telegramReport.ts @@ -0,0 +1,130 @@ +/** + * Telegram mirror for the secretary agent (영숙). + * + * After every company turn finishes, this helper takes the CEO synthesis + + * task list and pushes it to the user's Telegram chat — same behaviour as + * Connect_origin's `_handleCorporatePrompt` (lines ~20620): "Secretary + * automatically reports the round to the user." Connect AI had a Telegram + * integration but it was wired only for *inbound* messages — the company + * dispatcher we shipped earlier never called it. + * + * Design notes: + * - **Outbound-only**: no polling, no inbound side effects. Sends a single + * `sendMessage` per turn. + * - **Honours user opt-in**: requires `g1nation.telegram.enabled` to be + * true AND a stored bot token AND at least one allowed chat id. If any + * piece is missing, `buildTelegramReporter` returns `null` and the + * dispatcher silently skips the mirror — no log spam, no fake error. + * - **Per-call isolation**: each reporter fetches the token freshly via + * `context.secrets.get`, so rotating the secret in the settings UI + * takes effect on the *next* turn without a restart. + * - **Markdown-aware**: re-uses `TelegramHttpClient`, which already + * truncates to Telegram's 4096-char limit and falls back to plain text + * when the Markdown parser refuses a string. + */ +import * as vscode from 'vscode'; +import { logError, logInfo } from '../../utils'; +import { TelegramHttpClient } from '../../integrations/telegram/telegramClient'; +import { COMPANY_AGENTS } from './agents'; +import { AgentTurnOutput, CompanyState, CompanyTaskPlan } from './types'; + +/** Same key the rest of the extension uses. Defined locally so this module is dependency-free. */ +const TELEGRAM_TOKEN_SECRET_KEY = 'g1nation.telegram.botToken'; + +/** Maximum characters of the CEO report to embed verbatim in the Telegram message. */ +const REPORT_BUDGET = 2000; + +/** Reporter contract — call once per turn, returns whether delivery succeeded. */ +export type TelegramReporter = (text: string) => Promise; + +/** + * Build a Telegram reporter bound to the current user's bot config. Returns + * `null` when the user hasn't opted in (no token, no chat id, or + * `telegram.enabled` is false) — the dispatcher then treats it as "no + * mirror, nothing to do". + * + * The returned function targets the *first* allowed chat id. Users with + * multiple chats today still get one canonical destination; broader fan-out + * would be a feature add, not a port of origin's behaviour. + */ +export async function buildTelegramReporter( + context: vscode.ExtensionContext, +): Promise { + const cfg = vscode.workspace.getConfiguration('g1nation'); + if (!cfg.get('telegram.enabled', false)) return null; + + const token = ((await context.secrets.get(TELEGRAM_TOKEN_SECRET_KEY)) || '').trim(); + if (!token) { + logInfo('telegramReport: opt-in is on but no bot token stored; skipping mirror.'); + return null; + } + + const allowed = cfg.get('telegram.allowedChatIds', []) || []; + const chatId = allowed.find((id) => typeof id === 'number' && Number.isFinite(id) && id !== 0); + if (!chatId) { + logInfo('telegramReport: telegram enabled but allowedChatIds is empty; skipping mirror.'); + return null; + } + + // Build a thin client per turn — token capture happens once, so this + // doesn't keep a long-lived reference to the secret. + const client = new TelegramHttpClient({ getToken: () => token }); + + return async (text: string): Promise => { + if (!text || !text.trim()) return false; + try { + await client.sendMessage({ chatId, text, parseMode: 'Markdown' }); + return true; + } catch (e: any) { + // Fallback to plain text — Telegram rejects malformed Markdown + // (unbalanced asterisks, etc.). The origin does the same thing + // in `sendTelegramLong`, so behaviour stays familiar. + try { + const plain = text.replace(/[*_`[\]]/g, ''); + await client.sendMessage({ chatId, text: plain, parseMode: null }); + return true; + } catch (e2: any) { + logError('telegramReport: mirror failed in both modes.', { + primary: e?.message ?? String(e), + fallback: e2?.message ?? String(e2), + }); + return false; + } + } + }; +} + +/** + * 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. + * + * The function is pure (no I/O) and exported separately so it can be unit- + * tested without touching VS Code APIs. + */ +export function formatCompanyTelegramReport(opts: { + state: CompanyState; + userPrompt: string; + plan: CompanyTaskPlan; + outputs: AgentTurnOutput[]; + report: string; + sessionTimestamp: string; +}): string { + const company = opts.state.companyName || '1인 기업'; + const header = `*📱 ${company} — 작업 라운드 보고*`; + const cmdLine = `*명령:* ${opts.userPrompt.slice(0, 200)}`; + const brief = opts.plan.brief ? `\n\n*브리프:* ${opts.plan.brief}` : ''; + const agentsLine = opts.plan.tasks.length > 0 + ? '\n\n*완료한 에이전트:*\n' + opts.plan.tasks.map((t) => { + const def = COMPANY_AGENTS[t.agent]; + const ranOk = opts.outputs.find((o) => o.agentId === t.agent && !o.error); + const mark = ranOk ? '✅' : '⚠️'; + return `• ${mark} ${def?.emoji ?? ''} ${def?.name ?? t.agent}`; + }).join('\n') + : ''; + 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}`; +} diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 75e6024..e2ace34 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -1276,6 +1276,11 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn context: this._context, ai, defaultModel: cfg.defaultModel || 'gemma4:e2b', + // Hand the dispatcher a thunk into ConnectAI's action-tag + // executor so specialist outputs like `` actually + // hit disk. Without this, agents would *claim* to create + // files while nothing happened — the exact bug we just fixed. + executeActionTags: (text) => this._agent.executeActionTagsOnText(text), onEvent: emit, }); } catch (e: any) { @@ -1285,6 +1290,13 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn value: `1인 기업 모드 실행 실패: ${e?.message ?? e}`, }); } finally { + // The webview's send button is locked into the "generating" state + // when the user submits; it only unlocks on `streamEnd`. The + // normal chat path posts that from inside AgentExecutor, but + // the company turn never touches AgentExecutor, so we have to + // post it ourselves here — otherwise the input stays disabled + // with the red Stop button after the round completes. + this._view?.webview.postMessage({ type: 'streamEnd' }); void this._sendReadyStatus(); } }