refactor: Fine-tune sidebar interaction and refine company suite configuration

This commit is contained in:
2026-05-14 18:04:25 +09:00
parent 75d7e6b83a
commit d84e02c696
13 changed files with 215 additions and 33 deletions
@@ -1,5 +1,5 @@
{
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
"createdAt": 1778742370814,
"createdAt": 1778747831952,
"modelVersion": "unknown"
}
@@ -1,5 +1,5 @@
{
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
"createdAt": 1778742370811,
"createdAt": 1778747831950,
"modelVersion": "unknown"
}
@@ -1,5 +1,5 @@
{
"result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
"createdAt": 1778742370809,
"createdAt": 1778747831948,
"modelVersion": "unknown"
}
@@ -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"
}
@@ -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": {
+10
View File
@@ -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인 기업 모드를 위한 비즈니스 오케스트레이션 기능을 안정화하고, 부서 간 협업 로직을 강화했습니다.
+37 -10
View File
@@ -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,
+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.1.4",
"version": "2.1.5",
"publisher": "g1nation",
"license": "MIT",
"icon": "assets/icon.png",
+88 -7
View File
@@ -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<AgentRoleCategory>(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<CompanyState> | undefined): CompanyState {
roleCategoryOverrides[agentId] = v as AgentRoleCategory;
}
}
// 디스플레이 override — 이름·역할·이모지·색상 사용자 편집. 빈 필드는
// pruning, 알 수 없는 agent id는 drop.
const displayOverrides: Record<string, AgentDisplayOverride> = {};
if (raw.displayOverrides && typeof raw.displayOverrides === 'object') {
for (const [agentId, v] of Object.entries(raw.displayOverrides as Record<string, unknown>)) {
if (!knownId(agentId) || !v || typeof v !== 'object') continue;
const ov = v as Record<string, unknown>;
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<CompanyState> | 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;
}
/**
+2
View File
@@ -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,
+28
View File
@@ -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<string, AgentRoleCategory>;
/**
* 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<string, AgentDisplayOverride>;
/**
* User-defined work pipelines. When `activePipelineId` is set to a key
* here, the dispatcher runs the pipeline's stages in order instead of
+23
View File
@@ -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.
+11
View File
@@ -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,