From d84e02c696a7fb5c6ce5346f174d28b7b0c83801 Mon Sep 17 00:00:00 2001 From: g1nation Date: Thu, 14 May 2026 18:04:25 +0900 Subject: [PATCH] refactor: Fine-tune sidebar interaction and refine company suite configuration --- ...d46d2ca2057b05c488be1dcf439166ac5a9a1.json | 2 +- ...9f4f39d2bc368f77456c37b5eef9a94a66b5c.json | 2 +- ...5c7a44d7661af673b24e3f49551a7a2e50280.json | 2 +- ...adc543795e4b427b64540a49c9ab27c7fe213.json | 4 +- ...son => stress_conflict_1778747831929.json} | 20 ++-- PATCHNOTES.md | 10 ++ media/sidebar.js | 47 +++++++-- package.json | 2 +- src/features/company/companyConfig.ts | 95 +++++++++++++++++-- src/features/company/index.ts | 2 + src/features/company/types.ts | 28 ++++++ src/sidebar/chatHandlers.ts | 23 +++++ src/sidebarProvider.ts | 11 +++ 13 files changed, 215 insertions(+), 33 deletions(-) rename .astra/tests/stress/.astra/missions/{stress_conflict_1778742370793.json => stress_conflict_1778747831929.json} (81%) diff --git a/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json b/.astra/tests/stress/.astra/cache/259a37934ead3910a8722b82054d46d2ca2057b05c488be1dcf439166ac5a9a1.json index 86bf884..5e01eeb 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": 1778742370814, + "createdAt": 1778747831952, "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 5cd1838..d62f1dc 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": 1778742370811, + "createdAt": 1778747831950, "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 c7cf37b..598b4f4 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": 1778742370809, + "createdAt": 1778747831948, "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 8310ef7..c84f3e5 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_1778742370793\ndate: 2026-05-14T07:06:10.815Z\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]** 전략 수립 중... (14ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (3ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (3ms)\n", - "createdAt": 1778742370816, + "result": "---\nid: stress_conflict_1778747831929\ndate: 2026-05-14T08:37:11.953Z\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]** 전략 수립 중... (17ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (3ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (2ms)\n", + "createdAt": 1778747831953, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/missions/stress_conflict_1778742370793.json b/.astra/tests/stress/.astra/missions/stress_conflict_1778747831929.json similarity index 81% rename from .astra/tests/stress/.astra/missions/stress_conflict_1778742370793.json rename to .astra/tests/stress/.astra/missions/stress_conflict_1778747831929.json index 315a962..5aca48f 100644 --- a/.astra/tests/stress/.astra/missions/stress_conflict_1778742370793.json +++ b/.astra/tests/stress/.astra/missions/stress_conflict_1778747831929.json @@ -1,8 +1,8 @@ { - "missionId": "stress_conflict_1778742370793", + "missionId": "stress_conflict_1778747831929", "status": "completed", - "startTime": "2026-05-14T07:06:10.793Z", - "totalElapsedMs": 24, + "startTime": "2026-05-14T08:37:11.929Z", + "totalElapsedMs": 25, "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": 14, + "durationMs": 17, "message": "전략 수립 중...", - "ts": "2026-05-14T07:06:10.807Z" + "ts": "2026-05-14T08:37:11.946Z" }, { "from": "planner", "to": "researcher", "durationMs": 3, "message": "핵심 정보 수집 및 분석 중...", - "ts": "2026-05-14T07:06:10.810Z" + "ts": "2026-05-14T08:37:11.949Z" }, { "from": "researcher", "to": "writer", - "durationMs": 3, + "durationMs": 2, "message": "최종 리포트 작성 및 편집 중...", - "ts": "2026-05-14T07:06:10.813Z" + "ts": "2026-05-14T08:37:11.951Z" }, { "from": "writer", "to": "completed", - "durationMs": 4, + "durationMs": 3, "message": "미션 완료", - "ts": "2026-05-14T07:06:10.817Z" + "ts": "2026-05-14T08:37:11.954Z" } ], "resilienceMetrics": { diff --git a/PATCHNOTES.md b/PATCHNOTES.md index 32f2034..393b800 100644 --- a/PATCHNOTES.md +++ b/PATCHNOTES.md @@ -1,5 +1,15 @@ # Astra Patch Notes +## v2.1.5 (2026-05-14) +### 🚀 Orchestration Pipeline & Immersive Synergy +- **Pipeline Templates 도입:** 1인 기업 모드의 업무 흐름을 템플릿화하여 `pipelineTemplates.ts`를 통해 고정된 비즈니스 시나리오를 빠르게 실행할 수 있는 구조를 구축했습니다. +- **Orchestration 로직 정교화:** `CEO Planner`와 `Dispatcher` 간의 데이터 흐름을 최적화하여, 복합적인 미션 수행 시의 컨텍스트 유지 능력을 강화했습니다. +- **UI/UX 미세 조정:** 사이드바의 반응형 스타일과 상호작용 로직을 개선하여 더욱 쾌적한 에이전트 제어 환경을 제공합니다. +- **비즈니스 정합성 강화:** 롯데월드 이머시브 프로젝트와 같은 실제 기술 검토 시나리오를 수용할 수 있도록 에이전트의 지식 활용 범위를 조정했습니다. + +--- + + ## v2.1.4 (2026-05-14) ### 🏢 Enterprise Multi-Agent Suite & Reflector Stabilization - **Company Suite 고도화:** `CEO Planner`, `CEO Reporter` 등 1인 기업 모드를 위한 비즈니스 오케스트레이션 기능을 안정화하고, 부서 간 협업 로직을 강화했습니다. diff --git a/media/sidebar.js b/media/sidebar.js index e02e235..21e741b 100644 --- a/media/sidebar.js +++ b/media/sidebar.js @@ -2541,6 +2541,23 @@ return el; }; + // ── 디스플레이 필드 (이름·역할·이모지·색상) ── + // 빌트인이든 커스텀이든 사용자가 자유롭게 리네이밍 가능. 변경 후 + // CEO 보고서·planner enumeration·세션 로그 등 모든 표시 지점에 + // 즉시 반영된다 — resolveAgent가 override를 머지하므로. + const nameInput = _field('name', '이름 (Display Name)', false, a.name, a.defaultName, a.nameOverridden); + const roleInput = _field('role', '역할 (Role Title)', false, a.role, a.defaultRole, a.roleOverridden); + // 이모지·색상은 한 줄에 나란히 — CSS는 grid 없이 inline flex로 처리. + const visualWrap = document.createElement('div'); + visualWrap.style.cssText = 'display:flex; gap:8px;'; + const emojiInput = _field('emoji', '이모지', false, a.emoji, a.defaultEmoji, a.emojiOverridden); + const colorInput = _field('color', '색상 (#hex)', false, a.color, a.defaultColor, a.colorOverridden); + // 위에서 만든 두 필드는 editor에 이미 append됨. 한 줄로 묶고 싶으면 + // 부모에서 분리해 visualWrap에 다시 넣는다 — label은 직전 sibling. + // 더 단순하게: emoji/color 입력 후 reflow는 그냥 두고 max-width만 줄임. + emojiInput.style.maxWidth = '80px'; + colorInput.style.maxWidth = '120px'; + const tagInput = _field('tagline', 'Tagline (한 줄)', false, a.tagline, a.defaultTagline, a.taglineOverridden); const specInput = _field('specialty', 'Specialty (CEO가 dispatch 판단에 사용)', true, a.specialty, a.defaultSpecialty, a.specialtyOverridden); const persInput = _field('persona', 'Persona (말투·관점·강조)', true, a.persona, a.defaultPersona, a.personaOverridden); @@ -2553,13 +2570,10 @@ const resetBtn = document.createElement('button'); resetBtn.className = 'danger'; resetBtn.textContent = 'Reset'; - resetBtn.title = '이 에이전트의 모든 override 제거 → 디폴트로 복귀'; + resetBtn.title = '이 에이전트의 모든 override 제거 → 디폴트로 복귀 (이름·역할·프롬프트 전부)'; resetBtn.onclick = () => { - vscode.postMessage({ - type: 'setCompanyAgentPrompt', - agentId: a.id, - override: null, - }); + vscode.postMessage({ type: 'setCompanyAgentPrompt', agentId: a.id, override: null }); + vscode.postMessage({ type: 'setCompanyAgentDisplay', agentId: a.id, override: null }); }; const cancelBtn = document.createElement('button'); @@ -2573,10 +2587,23 @@ saveBtn.className = 'primary'; saveBtn.textContent = 'Save'; saveBtn.onclick = () => { - // Send what's currently in each field. The backend treats an - // empty string as "clear this field" (back to default), so - // typing nothing into Tagline + saving = Tagline default, - // Specialty + Persona untouched if not modified. + // 두 개의 message로 분리 전송 — display(name/role/emoji/color)와 + // prompt(tagline/specialty/persona)는 백엔드에서 서로 다른 + // override 테이블을 쓴다. 빈 문자열은 그 필드만 reset, 디폴트와 + // 같은 값은 override 안 함(원본 그대로 두기) — 사용자가 일부러 + // 디폴트 값을 다시 입력해 "override 박제"하는 케이스는 드물고, + // 안 박는 게 다음 코드 업데이트 시 새 디폴트를 자동 흡수해서 + // 유리하다. + vscode.postMessage({ + type: 'setCompanyAgentDisplay', + agentId: a.id, + override: { + name: nameInput.value === a.defaultName ? '' : nameInput.value, + role: roleInput.value === a.defaultRole ? '' : roleInput.value, + emoji: emojiInput.value === a.defaultEmoji ? '' : emojiInput.value, + color: colorInput.value === a.defaultColor ? '' : colorInput.value, + }, + }); vscode.postMessage({ type: 'setCompanyAgentPrompt', agentId: a.id, diff --git a/package.json b/package.json index f3a040c..ff1af31 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.1.4", + "version": "2.1.5", "publisher": "g1nation", "license": "MIT", "icon": "assets/icon.png", diff --git a/src/features/company/companyConfig.ts b/src/features/company/companyConfig.ts index c1ad28f..8f59e9a 100644 --- a/src/features/company/companyConfig.ts +++ b/src/features/company/companyConfig.ts @@ -21,8 +21,8 @@ import * as vscode from 'vscode'; import { COMPANY_AGENTS, DEFAULT_ACTIVE_AGENTS, getCompanyAgent } from './agents'; import { - AgentPromptOverride, AgentRoleCategory, CompanyAgentDef, CompanyState, COMPANY_STATE_KEY, - PipelineDef, PipelineStage, ROLE_CATEGORY_ORDER, + AgentDisplayOverride, AgentPromptOverride, AgentRoleCategory, CompanyAgentDef, + CompanyState, COMPANY_STATE_KEY, PipelineDef, PipelineStage, ROLE_CATEGORY_ORDER, } from './types'; const VALID_ROLE_CATEGORIES = new Set(ROLE_CATEGORY_ORDER); @@ -91,6 +91,7 @@ function _defaultState(): CompanyState { knowledgeMixOverrides: {}, customAgents: {}, roleCategoryOverrides: {}, + displayOverrides: {}, pipelines: {}, activePipelineId: null, }; @@ -244,6 +245,26 @@ function _normalize(raw: Partial | undefined): CompanyState { roleCategoryOverrides[agentId] = v as AgentRoleCategory; } } + // 디스플레이 override — 이름·역할·이모지·색상 사용자 편집. 빈 필드는 + // pruning, 알 수 없는 agent id는 drop. + const displayOverrides: Record = {}; + if (raw.displayOverrides && typeof raw.displayOverrides === 'object') { + for (const [agentId, v] of Object.entries(raw.displayOverrides as Record)) { + if (!knownId(agentId) || !v || typeof v !== 'object') continue; + const ov = v as Record; + const cleaned: AgentDisplayOverride = {}; + if (typeof ov.name === 'string' && ov.name.trim()) cleaned.name = ov.name.trim(); + if (typeof ov.role === 'string' && ov.role.trim()) cleaned.role = ov.role.trim(); + if (typeof ov.emoji === 'string' && ov.emoji.trim()) cleaned.emoji = ov.emoji.trim(); + if (typeof ov.color === 'string' && /^#?[0-9a-fA-F]{3,8}$/.test(ov.color.trim())) { + const c = ov.color.trim(); + cleaned.color = c.startsWith('#') ? c : '#' + c; + } + if (cleaned.name || cleaned.role || cleaned.emoji || cleaned.color) { + displayOverrides[agentId] = cleaned; + } + } + } // Pipelines — drop malformed entries; stage agent ids that don't resolve // are kept (the dispatcher will surface a per-stage error) so the user // can fix them in the editor instead of losing their pipeline silently. @@ -266,6 +287,7 @@ function _normalize(raw: Partial | undefined): CompanyState { knowledgeMixOverrides, customAgents, roleCategoryOverrides, + displayOverrides, pipelines, activePipelineId, }; @@ -456,15 +478,64 @@ export async function removeCustomAgent( const { [agentId]: _p, ...promptOverrides } = cur.promptOverrides; const { [agentId]: _k, ...knowledgeMixOverrides } = cur.knowledgeMixOverrides; const { [agentId]: _r, ...roleCategoryOverrides } = (cur.roleCategoryOverrides ?? {}); + const { [agentId]: _d, ...displayOverrides } = (cur.displayOverrides ?? {}); const activeAgentIds = cur.activeAgentIds.filter((id) => id !== agentId); const next: CompanyState = { ...cur, customAgents, modelOverrides, promptOverrides, knowledgeMixOverrides, - roleCategoryOverrides, activeAgentIds, + roleCategoryOverrides, displayOverrides, activeAgentIds, }; await writeCompanyState(context, next); return { ok: true, state: next }; } +/** + * Set / clear an agent's display-identity override (name / role / emoji / + * color). Each field follows the same semantics as `setAgentPromptOverride`: + * - non-empty string → save as override + * - empty string / null / undefined → *clear* that sub-field + * Passing `null` for the whole override clears every field at once. + * Built-in CEO can be renamed too — there's no reason to lock the chip text. + */ +export async function setAgentDisplayOverride( + context: vscode.ExtensionContext, + agentId: string, + override: AgentDisplayOverride | null, +): Promise<{ ok: true; state: CompanyState } | { ok: false; reason: string }> { + const cur = readCompanyState(context); + if (!getCompanyAgent(agentId) && !cur.customAgents?.[agentId]) { + return { ok: false, reason: `'${agentId}' 에이전트를 찾을 수 없습니다.` }; + } + const overrides = { ...(cur.displayOverrides ?? {}) }; + if (!override) { + delete overrides[agentId]; + } else { + const existing: AgentDisplayOverride = { ...(overrides[agentId] ?? {}) }; + for (const key of ['name', 'role', 'emoji', 'color'] as const) { + const v = override[key]; + if (v === undefined) continue; + if (typeof v === 'string' && v.trim()) { + if (key === 'color') { + if (!/^#?[0-9a-fA-F]{3,8}$/.test(v.trim())) continue; + const c = v.trim(); + existing[key] = c.startsWith('#') ? c : '#' + c; + } else { + existing[key] = v.trim(); + } + } else { + delete existing[key]; + } + } + if (existing.name || existing.role || existing.emoji || existing.color) { + overrides[agentId] = existing; + } else { + delete overrides[agentId]; + } + } + const next: CompanyState = { ...cur, displayOverrides: overrides }; + await writeCompanyState(context, next); + return { ok: true, state: next }; +} + /** * Set / clear a per-agent 직군 override. Pass `null` to revert the agent * to its def's own roleCategory. The CEO can't be reclassified (always 'ceo'). @@ -569,11 +640,21 @@ export function resolveActivePipeline(state: CompanyState): PipelineDef | null { export function resolveAgent(state: CompanyState, agentId: string): CompanyAgentDef | undefined { const base = getCompanyAgent(agentId) ?? state.customAgents?.[agentId]; if (!base) return undefined; - const ov = state.roleCategoryOverrides?.[agentId]; - if (ov && VALID_ROLE_CATEGORIES.has(ov) && ov !== base.roleCategory && agentId !== 'ceo') { - return { ...base, roleCategory: ov }; + const display = state.displayOverrides?.[agentId]; + const roleOv = state.roleCategoryOverrides?.[agentId]; + // 머지 필요 없으면 base를 그대로 반환 — 불필요한 객체 복제 방지. + const hasDisplay = !!(display && (display.name || display.role || display.emoji || display.color)); + const hasRole = !!(roleOv && VALID_ROLE_CATEGORIES.has(roleOv) && roleOv !== base.roleCategory && agentId !== 'ceo'); + if (!hasDisplay && !hasRole) return base; + const merged: CompanyAgentDef = { ...base }; + if (hasDisplay) { + if (display!.name) merged.name = display!.name; + if (display!.role) merged.role = display!.role; + if (display!.emoji) merged.emoji = display!.emoji; + if (display!.color) merged.color = display!.color; } - return base; + if (hasRole) merged.roleCategory = roleOv!; + return merged; } /** diff --git a/src/features/company/index.ts b/src/features/company/index.ts index d7be8fa..099b5f8 100644 --- a/src/features/company/index.ts +++ b/src/features/company/index.ts @@ -26,6 +26,7 @@ export { removeCustomAgent, validateCustomAgentId, setAgentRoleCategory, + setAgentDisplayOverride, upsertPipeline, deletePipeline, setActivePipeline, @@ -43,6 +44,7 @@ export { } from './companyConfig'; export type { + AgentDisplayOverride, AgentRoleCategory, PipelineDef, PipelineStage, diff --git a/src/features/company/types.ts b/src/features/company/types.ts index d6062f6..d02bfc6 100644 --- a/src/features/company/types.ts +++ b/src/features/company/types.ts @@ -102,6 +102,27 @@ export interface AgentPromptOverride { tagline?: string; } +/** + * User edits to an agent's *identity* surface (name / role title / emoji / + * color). Unlike prompt overrides these don't affect the LLM's behaviour + * directly — they change how the agent is *referred to* across the UI, + * planner enumeration, and CEO report. Built-ins ship with fixed defaults; + * the user can rename "레오" to "Lily" via this override without forking + * the shipped roster. CEO planner's `_canonicalAgentId` already matches + * against the effective (override-applied) name, so renaming an agent + * still lets the LLM dispatch to it correctly. + */ +export interface AgentDisplayOverride { + /** Replaces the display name (e.g. "레오" → "Lily"). */ + name?: string; + /** Replaces the role title (e.g. "Head of YouTube" → "콘텐츠 디렉터"). */ + role?: string; + /** Replaces the single emoji icon. */ + emoji?: string; + /** Replaces the brand color (CSS hex). */ + color?: string; +} + /** * Persisted runtime state for the company mode. Stored in VS Code's * `globalState` plus a small JSON file under `.astra/company/_shared/`. @@ -154,6 +175,13 @@ export interface CompanyState { * agent def's own `roleCategory`. */ roleCategoryOverrides?: Record; + /** + * Per-agent identity overrides (name / role / emoji / color). Same + * semantics as `promptOverrides` but for the display surface — the + * effective values are merged into the def at read time via + * `resolveAgent`. + */ + displayOverrides?: Record; /** * User-defined work pipelines. When `activePipelineId` is set to a key * here, the dispatcher runs the pipeline's stages in order instead of diff --git a/src/sidebar/chatHandlers.ts b/src/sidebar/chatHandlers.ts index fa6f266..3a09652 100644 --- a/src/sidebar/chatHandlers.ts +++ b/src/sidebar/chatHandlers.ts @@ -206,6 +206,29 @@ export async function handleChatMessage(provider: SidebarChatProvider, data: any } return true; } + case 'setCompanyAgentDisplay': { + // 이름/역할/이모지/색상 override. 페이로드는 setCompanyAgentPrompt와 + // 동일한 패턴 — null이면 전체 reset, 각 필드 빈 문자열이면 그 필드만 reset. + const { setAgentDisplayOverride } = await import('../features/company'); + const agentId = typeof data.agentId === 'string' ? data.agentId : ''; + if (!agentId) return true; + const v = data.override; + const override = v === null + ? null + : { + name: typeof v?.name === 'string' ? v.name : undefined, + role: typeof v?.role === 'string' ? v.role : undefined, + emoji: typeof v?.emoji === 'string' ? v.emoji : undefined, + color: typeof v?.color === 'string' ? v.color : undefined, + }; + const result = await setAgentDisplayOverride(provider._context, agentId, override); + provider._view?.webview.postMessage({ + type: 'setCompanyAgentDisplayResult', + value: result.ok ? { ok: true, agentId } : { ok: false, reason: result.reason }, + }); + if (result.ok) await provider._sendCompanyAgents(); + return true; + } case 'setCompanyAgentRoleCategory': { // Override an agent's 직군. Empty / null payload value reverts to // the def's own roleCategory. CEO is rejected by the backend. diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 009564e..e808481 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -1599,6 +1599,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn const kmOverride = state.knowledgeMixOverrides[id]; const hasKmOverride = typeof kmOverride === 'number'; const roleOverride = state.roleCategoryOverrides?.[id]; + const displayOv = state.displayOverrides?.[id] || {}; return { id, name: effective.name, @@ -1618,6 +1619,16 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn personaOverridden: !!override.persona, specialtyOverridden: !!override.specialty, taglineOverridden: !!override.tagline, + // 디스플레이 override — Edit 폼이 default vs current를 비교해 + // dirty 표시할 수 있도록 baseDef의 원본 값도 같이 보낸다. + defaultName: baseDef.name, + defaultRole: baseDef.role, + defaultEmoji: baseDef.emoji, + defaultColor: baseDef.color, + nameOverridden: !!displayOv.name, + roleOverridden: !!displayOv.role, + emojiOverridden: !!displayOv.emoji, + colorOverridden: !!displayOv.color, // 직군: effective(override 반영) + def 기본값 + override 플래그 roleCategory: effective.roleCategory, defaultRoleCategory: baseDef.roleCategory,