refactor: Fine-tune sidebar interaction and refine company suite configuration
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": 1778742370814,
|
"createdAt": 1778747831952,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
||||||
"createdAt": 1778742370811,
|
"createdAt": 1778747831950,
|
||||||
"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": 1778742370809,
|
"createdAt": 1778747831948,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+2
-2
@@ -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",
|
"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": 1778742370816,
|
"createdAt": 1778747831953,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+10
-10
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"missionId": "stress_conflict_1778742370793",
|
"missionId": "stress_conflict_1778747831929",
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"startTime": "2026-05-14T07:06:10.793Z",
|
"startTime": "2026-05-14T08:37:11.929Z",
|
||||||
"totalElapsedMs": 24,
|
"totalElapsedMs": 25,
|
||||||
"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": 14,
|
"durationMs": 17,
|
||||||
"message": "전략 수립 중...",
|
"message": "전략 수립 중...",
|
||||||
"ts": "2026-05-14T07:06:10.807Z"
|
"ts": "2026-05-14T08:37:11.946Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "planner",
|
"from": "planner",
|
||||||
"to": "researcher",
|
"to": "researcher",
|
||||||
"durationMs": 3,
|
"durationMs": 3,
|
||||||
"message": "핵심 정보 수집 및 분석 중...",
|
"message": "핵심 정보 수집 및 분석 중...",
|
||||||
"ts": "2026-05-14T07:06:10.810Z"
|
"ts": "2026-05-14T08:37:11.949Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "researcher",
|
"from": "researcher",
|
||||||
"to": "writer",
|
"to": "writer",
|
||||||
"durationMs": 3,
|
"durationMs": 2,
|
||||||
"message": "최종 리포트 작성 및 편집 중...",
|
"message": "최종 리포트 작성 및 편집 중...",
|
||||||
"ts": "2026-05-14T07:06:10.813Z"
|
"ts": "2026-05-14T08:37:11.951Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "writer",
|
"from": "writer",
|
||||||
"to": "completed",
|
"to": "completed",
|
||||||
"durationMs": 4,
|
"durationMs": 3,
|
||||||
"message": "미션 완료",
|
"message": "미션 완료",
|
||||||
"ts": "2026-05-14T07:06:10.817Z"
|
"ts": "2026-05-14T08:37:11.954Z"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"resilienceMetrics": {
|
"resilienceMetrics": {
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
# Astra Patch Notes
|
# 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)
|
## v2.1.4 (2026-05-14)
|
||||||
### 🏢 Enterprise Multi-Agent Suite & Reflector Stabilization
|
### 🏢 Enterprise Multi-Agent Suite & Reflector Stabilization
|
||||||
- **Company Suite 고도화:** `CEO Planner`, `CEO Reporter` 등 1인 기업 모드를 위한 비즈니스 오케스트레이션 기능을 안정화하고, 부서 간 협업 로직을 강화했습니다.
|
- **Company Suite 고도화:** `CEO Planner`, `CEO Reporter` 등 1인 기업 모드를 위한 비즈니스 오케스트레이션 기능을 안정화하고, 부서 간 협업 로직을 강화했습니다.
|
||||||
|
|||||||
+37
-10
@@ -2541,6 +2541,23 @@
|
|||||||
return el;
|
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 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 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);
|
const persInput = _field('persona', 'Persona (말투·관점·강조)', true, a.persona, a.defaultPersona, a.personaOverridden);
|
||||||
@@ -2553,13 +2570,10 @@
|
|||||||
const resetBtn = document.createElement('button');
|
const resetBtn = document.createElement('button');
|
||||||
resetBtn.className = 'danger';
|
resetBtn.className = 'danger';
|
||||||
resetBtn.textContent = 'Reset';
|
resetBtn.textContent = 'Reset';
|
||||||
resetBtn.title = '이 에이전트의 모든 override 제거 → 디폴트로 복귀';
|
resetBtn.title = '이 에이전트의 모든 override 제거 → 디폴트로 복귀 (이름·역할·프롬프트 전부)';
|
||||||
resetBtn.onclick = () => {
|
resetBtn.onclick = () => {
|
||||||
vscode.postMessage({
|
vscode.postMessage({ type: 'setCompanyAgentPrompt', agentId: a.id, override: null });
|
||||||
type: 'setCompanyAgentPrompt',
|
vscode.postMessage({ type: 'setCompanyAgentDisplay', agentId: a.id, override: null });
|
||||||
agentId: a.id,
|
|
||||||
override: null,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelBtn = document.createElement('button');
|
const cancelBtn = document.createElement('button');
|
||||||
@@ -2573,10 +2587,23 @@
|
|||||||
saveBtn.className = 'primary';
|
saveBtn.className = 'primary';
|
||||||
saveBtn.textContent = 'Save';
|
saveBtn.textContent = 'Save';
|
||||||
saveBtn.onclick = () => {
|
saveBtn.onclick = () => {
|
||||||
// Send what's currently in each field. The backend treats an
|
// 두 개의 message로 분리 전송 — display(name/role/emoji/color)와
|
||||||
// empty string as "clear this field" (back to default), so
|
// prompt(tagline/specialty/persona)는 백엔드에서 서로 다른
|
||||||
// typing nothing into Tagline + saving = Tagline default,
|
// override 테이블을 쓴다. 빈 문자열은 그 필드만 reset, 디폴트와
|
||||||
// Specialty + Persona untouched if not modified.
|
// 같은 값은 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({
|
vscode.postMessage({
|
||||||
type: 'setCompanyAgentPrompt',
|
type: 'setCompanyAgentPrompt',
|
||||||
agentId: a.id,
|
agentId: a.id,
|
||||||
|
|||||||
+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.1.4",
|
"version": "2.1.5",
|
||||||
"publisher": "g1nation",
|
"publisher": "g1nation",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"icon": "assets/icon.png",
|
"icon": "assets/icon.png",
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { COMPANY_AGENTS, DEFAULT_ACTIVE_AGENTS, getCompanyAgent } from './agents';
|
import { COMPANY_AGENTS, DEFAULT_ACTIVE_AGENTS, getCompanyAgent } from './agents';
|
||||||
import {
|
import {
|
||||||
AgentPromptOverride, AgentRoleCategory, CompanyAgentDef, CompanyState, COMPANY_STATE_KEY,
|
AgentDisplayOverride, AgentPromptOverride, AgentRoleCategory, CompanyAgentDef,
|
||||||
PipelineDef, PipelineStage, ROLE_CATEGORY_ORDER,
|
CompanyState, COMPANY_STATE_KEY, PipelineDef, PipelineStage, ROLE_CATEGORY_ORDER,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
const VALID_ROLE_CATEGORIES = new Set<AgentRoleCategory>(ROLE_CATEGORY_ORDER);
|
const VALID_ROLE_CATEGORIES = new Set<AgentRoleCategory>(ROLE_CATEGORY_ORDER);
|
||||||
@@ -91,6 +91,7 @@ function _defaultState(): CompanyState {
|
|||||||
knowledgeMixOverrides: {},
|
knowledgeMixOverrides: {},
|
||||||
customAgents: {},
|
customAgents: {},
|
||||||
roleCategoryOverrides: {},
|
roleCategoryOverrides: {},
|
||||||
|
displayOverrides: {},
|
||||||
pipelines: {},
|
pipelines: {},
|
||||||
activePipelineId: null,
|
activePipelineId: null,
|
||||||
};
|
};
|
||||||
@@ -244,6 +245,26 @@ function _normalize(raw: Partial<CompanyState> | undefined): CompanyState {
|
|||||||
roleCategoryOverrides[agentId] = v as AgentRoleCategory;
|
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
|
// Pipelines — drop malformed entries; stage agent ids that don't resolve
|
||||||
// are kept (the dispatcher will surface a per-stage error) so the user
|
// 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.
|
// can fix them in the editor instead of losing their pipeline silently.
|
||||||
@@ -266,6 +287,7 @@ function _normalize(raw: Partial<CompanyState> | undefined): CompanyState {
|
|||||||
knowledgeMixOverrides,
|
knowledgeMixOverrides,
|
||||||
customAgents,
|
customAgents,
|
||||||
roleCategoryOverrides,
|
roleCategoryOverrides,
|
||||||
|
displayOverrides,
|
||||||
pipelines,
|
pipelines,
|
||||||
activePipelineId,
|
activePipelineId,
|
||||||
};
|
};
|
||||||
@@ -456,15 +478,64 @@ export async function removeCustomAgent(
|
|||||||
const { [agentId]: _p, ...promptOverrides } = cur.promptOverrides;
|
const { [agentId]: _p, ...promptOverrides } = cur.promptOverrides;
|
||||||
const { [agentId]: _k, ...knowledgeMixOverrides } = cur.knowledgeMixOverrides;
|
const { [agentId]: _k, ...knowledgeMixOverrides } = cur.knowledgeMixOverrides;
|
||||||
const { [agentId]: _r, ...roleCategoryOverrides } = (cur.roleCategoryOverrides ?? {});
|
const { [agentId]: _r, ...roleCategoryOverrides } = (cur.roleCategoryOverrides ?? {});
|
||||||
|
const { [agentId]: _d, ...displayOverrides } = (cur.displayOverrides ?? {});
|
||||||
const activeAgentIds = cur.activeAgentIds.filter((id) => id !== agentId);
|
const activeAgentIds = cur.activeAgentIds.filter((id) => id !== agentId);
|
||||||
const next: CompanyState = {
|
const next: CompanyState = {
|
||||||
...cur, customAgents, modelOverrides, promptOverrides, knowledgeMixOverrides,
|
...cur, customAgents, modelOverrides, promptOverrides, knowledgeMixOverrides,
|
||||||
roleCategoryOverrides, activeAgentIds,
|
roleCategoryOverrides, displayOverrides, activeAgentIds,
|
||||||
};
|
};
|
||||||
await writeCompanyState(context, next);
|
await writeCompanyState(context, next);
|
||||||
return { ok: true, state: 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
|
* 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').
|
* 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 {
|
export function resolveAgent(state: CompanyState, agentId: string): CompanyAgentDef | undefined {
|
||||||
const base = getCompanyAgent(agentId) ?? state.customAgents?.[agentId];
|
const base = getCompanyAgent(agentId) ?? state.customAgents?.[agentId];
|
||||||
if (!base) return undefined;
|
if (!base) return undefined;
|
||||||
const ov = state.roleCategoryOverrides?.[agentId];
|
const display = state.displayOverrides?.[agentId];
|
||||||
if (ov && VALID_ROLE_CATEGORIES.has(ov) && ov !== base.roleCategory && agentId !== 'ceo') {
|
const roleOv = state.roleCategoryOverrides?.[agentId];
|
||||||
return { ...base, roleCategory: ov };
|
// 머지 필요 없으면 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export {
|
|||||||
removeCustomAgent,
|
removeCustomAgent,
|
||||||
validateCustomAgentId,
|
validateCustomAgentId,
|
||||||
setAgentRoleCategory,
|
setAgentRoleCategory,
|
||||||
|
setAgentDisplayOverride,
|
||||||
upsertPipeline,
|
upsertPipeline,
|
||||||
deletePipeline,
|
deletePipeline,
|
||||||
setActivePipeline,
|
setActivePipeline,
|
||||||
@@ -43,6 +44,7 @@ export {
|
|||||||
} from './companyConfig';
|
} from './companyConfig';
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
|
AgentDisplayOverride,
|
||||||
AgentRoleCategory,
|
AgentRoleCategory,
|
||||||
PipelineDef,
|
PipelineDef,
|
||||||
PipelineStage,
|
PipelineStage,
|
||||||
|
|||||||
@@ -102,6 +102,27 @@ export interface AgentPromptOverride {
|
|||||||
tagline?: string;
|
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
|
* Persisted runtime state for the company mode. Stored in VS Code's
|
||||||
* `globalState` plus a small JSON file under `.astra/company/_shared/`.
|
* `globalState` plus a small JSON file under `.astra/company/_shared/`.
|
||||||
@@ -154,6 +175,13 @@ export interface CompanyState {
|
|||||||
* agent def's own `roleCategory`.
|
* agent def's own `roleCategory`.
|
||||||
*/
|
*/
|
||||||
roleCategoryOverrides?: Record<string, AgentRoleCategory>;
|
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
|
* User-defined work pipelines. When `activePipelineId` is set to a key
|
||||||
* here, the dispatcher runs the pipeline's stages in order instead of
|
* here, the dispatcher runs the pipeline's stages in order instead of
|
||||||
|
|||||||
@@ -206,6 +206,29 @@ export async function handleChatMessage(provider: SidebarChatProvider, data: any
|
|||||||
}
|
}
|
||||||
return true;
|
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': {
|
case 'setCompanyAgentRoleCategory': {
|
||||||
// Override an agent's 직군. Empty / null payload value reverts to
|
// Override an agent's 직군. Empty / null payload value reverts to
|
||||||
// the def's own roleCategory. CEO is rejected by the backend.
|
// the def's own roleCategory. CEO is rejected by the backend.
|
||||||
|
|||||||
@@ -1599,6 +1599,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
const kmOverride = state.knowledgeMixOverrides[id];
|
const kmOverride = state.knowledgeMixOverrides[id];
|
||||||
const hasKmOverride = typeof kmOverride === 'number';
|
const hasKmOverride = typeof kmOverride === 'number';
|
||||||
const roleOverride = state.roleCategoryOverrides?.[id];
|
const roleOverride = state.roleCategoryOverrides?.[id];
|
||||||
|
const displayOv = state.displayOverrides?.[id] || {};
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: effective.name,
|
name: effective.name,
|
||||||
@@ -1618,6 +1619,16 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
personaOverridden: !!override.persona,
|
personaOverridden: !!override.persona,
|
||||||
specialtyOverridden: !!override.specialty,
|
specialtyOverridden: !!override.specialty,
|
||||||
taglineOverridden: !!override.tagline,
|
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 플래그
|
// 직군: effective(override 반영) + def 기본값 + override 플래그
|
||||||
roleCategory: effective.roleCategory,
|
roleCategory: effective.roleCategory,
|
||||||
defaultRoleCategory: baseDef.roleCategory,
|
defaultRoleCategory: baseDef.roleCategory,
|
||||||
|
|||||||
Reference in New Issue
Block a user