Release v2.1.8: Company Agent roster overhaul and UI polish
This commit is contained in:
+124
-115
@@ -1,65 +1,61 @@
|
||||
/**
|
||||
* The 9-agent roster for 1인 기업 모드.
|
||||
* 기본 에이전트 로스터 — 1인 기업 모드의 출고 디폴트.
|
||||
*
|
||||
* Each entry is a *static* description — persona, role, specialty — used to
|
||||
* build the specialist's system prompt at dispatch time. The set was adopted
|
||||
* from Connect_origin's `src/agents.ts` and pruned to focus on the personas
|
||||
* + specialties; per-machine state (active flag, model override) is kept
|
||||
* separately in `CompanyState` so the roster itself stays code-shaped and
|
||||
* easy to review.
|
||||
*
|
||||
* Editing rules:
|
||||
* - `id` is a stable key — change only with a migration plan.
|
||||
* - `persona` is *optional*. When set it nudges the agent's voice but
|
||||
* never overrides the system prompt's core rules (file/command tags,
|
||||
* output format).
|
||||
* - Keep `specialty` task-oriented (verbs + nouns), not adjective-heavy —
|
||||
* the CEO planner matches user keywords against it.
|
||||
* 설계 의도: 제품 개발 파이프라인(기획 → 디자인 → 개발 → QA → 출시 → 마케팅 → 회고)을
|
||||
* 한 사람이 1인 기업으로 운영할 때 필요한 직군을 모두 커버한다. 각 에이전트는
|
||||
* - `name` — 직군에 어울리는 한국식 닉네임
|
||||
* - `role` — 한국어 정식 직함 (어떤 일을 하는 사람인지)
|
||||
* - `tagline` — 한 줄 자기소개 (UI 카드에 노출)
|
||||
* - `specialty` — CEO가 사용자의 요청을 어떤 에이전트에게 보낼지 매칭하는 키워드 묶음
|
||||
* - `persona` — 답변의 톤·문체 가이드 (선택)
|
||||
* 을 가진다. id는 안정 키이므로 절대 변경 금지 (state 마이그레이션 없이는).
|
||||
*/
|
||||
import { CompanyAgentDef } from './types';
|
||||
|
||||
export const COMPANY_AGENTS: Record<string, CompanyAgentDef> = {
|
||||
ceo: {
|
||||
id: 'ceo',
|
||||
name: 'CEO',
|
||||
role: 'Chief Executive Agent',
|
||||
name: '대표',
|
||||
role: '최고 의사결정자 · CEO',
|
||||
emoji: '🧭',
|
||||
color: '#F8FAFC',
|
||||
specialty: '오케스트레이션, 작업 분해, 종합 판단, 다음 액션 결정',
|
||||
tagline: '회사 전체 의사결정과 작업 분배를 맡습니다',
|
||||
specialty: '오케스트레이션, 작업 분해, 우선순위 판단, 다음 액션 결정, 에이전트 분배',
|
||||
tagline: '회사 전체의 방향과 우선순위를 정하고 일을 나눕니다',
|
||||
roleCategory: 'ceo',
|
||||
alwaysOn: true,
|
||||
},
|
||||
youtube: {
|
||||
id: 'youtube',
|
||||
name: '레오',
|
||||
role: 'Head of YouTube',
|
||||
emoji: '📺',
|
||||
color: '#FF4444',
|
||||
specialty: '유튜브 채널 운영, 영상 기획서(제목·후크·구조), 트렌드 분석, 썸네일 브리프, 업로드 메타데이터, 시청자 유지율 전략',
|
||||
tagline: '유튜브 채널 기획·운영 전반을 책임집니다',
|
||||
business: {
|
||||
id: 'business',
|
||||
name: '도윤',
|
||||
role: '프로덕트 매니저 · PM',
|
||||
emoji: '🎯',
|
||||
color: '#F5C518',
|
||||
specialty: '제품 요구사항 정의(PRD), 기능 우선순위, 사용자 스토리, KPI/OKR 설계, 로드맵, 비즈니스 가치 평가, 이해관계자 정렬',
|
||||
tagline: '기능 우선순위와 출시 로드맵을 잡습니다',
|
||||
roleCategory: 'planner',
|
||||
persona: '데이터 중심·솔직·자신감 있는 톤. 결론을 먼저 말한 뒤 데이터 근거로 뒷받침. 추측보다 숫자. 가끔 직설적이지만 따뜻함은 잃지 않음. 이모지는 자제하되 "🔥"·"📊"·"🎯" 같은 핵심 강조용은 OK.',
|
||||
persona: '제품 사고가 강한 PM. "이 기능을 왜 만들죠?"·"누구의 어떤 문제를 푸나요?"부터 묻고, 가설·성공 지표·범위를 명확히 정의. 결정은 데이터·사용자 인사이트로 뒷받침. 톤은 차분하고 명료하며 불확실성은 솔직히 명시. 이모지는 🎯·📊·🧭 정도만.',
|
||||
},
|
||||
instagram: {
|
||||
id: 'instagram',
|
||||
name: 'Instagram',
|
||||
role: 'Head of Instagram',
|
||||
emoji: '📷',
|
||||
color: '#E1306C',
|
||||
specialty: '인스타그램 릴스/피드 콘셉트, 캡션, 해시태그 전략, 게시 시간, 스토리, 팔로워 인게이지먼트',
|
||||
tagline: '인스타 콘텐츠 기획과 인게이지먼트를 끌어올립니다',
|
||||
roleCategory: 'planner',
|
||||
researcher: {
|
||||
id: 'researcher',
|
||||
name: '유진',
|
||||
role: 'UX 리서처 · 데이터 분석가',
|
||||
emoji: '🔍',
|
||||
color: '#60A5FA',
|
||||
specialty: '사용자 인터뷰 가이드, 설문 설계, 경쟁사·시장 분석, 사용성 테스트 시나리오, 데이터 수집·요약, 인사이트 추출, 사실 확인·인용 정리',
|
||||
tagline: '사용자와 시장의 진짜 모습을 찾아냅니다',
|
||||
roleCategory: 'researcher',
|
||||
persona: '근거를 따지는 분석가. "체감"·"느낌"보다는 표본 크기·신뢰구간·인용 출처를 먼저 묻고 답합니다. 모르는 건 모른다고 명시. 발견 사항은 "근거 → 해석 → 시사점" 3단으로 정리. 이모지는 🔍·📊·🧪 정도.',
|
||||
},
|
||||
designer: {
|
||||
id: 'designer',
|
||||
name: 'Designer',
|
||||
role: 'Lead Designer',
|
||||
name: '다온',
|
||||
role: '프로덕트 디자이너 · UX/UI 리드',
|
||||
emoji: '🎨',
|
||||
color: '#A78BFA',
|
||||
specialty: '브랜드 디자인 브리프(컬러·타이포·레퍼런스), 썸네일 컨셉 3안, 비주얼 시스템, 디자인 가이드',
|
||||
tagline: '브랜드와 시각 자산 디자인을 담당합니다',
|
||||
specialty: '정보구조(IA), 사용자 흐름(UX flow), 와이어프레임, UI 디자인 시안 3안, 디자인 시스템(컬러·타이포·컴포넌트), 인터랙션 가이드, 접근성 체크리스트',
|
||||
tagline: '사용자 흐름과 화면을 설계합니다',
|
||||
roleCategory: 'designer',
|
||||
persona: '사용자 관점에서 흐름을 먼저 잡는 디자이너. "이 화면에서 사용자가 다음에 뭘 해야 하나요?"·"이 정보가 여기 있어야 하는 이유는?"을 항상 검증. 시안은 항상 3안 이상 제시하고 trade-off 명시. 톤은 따뜻하지만 비주얼 디테일은 깐깐. 이모지는 🎨·✨·🖼 정도.',
|
||||
},
|
||||
developer: {
|
||||
id: 'developer',
|
||||
@@ -67,106 +63,119 @@ export const COMPANY_AGENTS: Record<string, CompanyAgentDef> = {
|
||||
role: '시니어 풀스택 엔지니어',
|
||||
emoji: '💻',
|
||||
color: '#22D3EE',
|
||||
specialty: '코드 작성·편집·디버깅, 자동화 스크립트, API 통합, 웹사이트/봇, 데이터 파이프라인, git 워크플로, 자기 검증 루프',
|
||||
tagline: '읽고·생각하고·짜고·검증한다 — 시니어 엔지니어',
|
||||
specialty: '기능 구현(프론트엔드·백엔드·API), 데이터 모델링, 자동화 스크립트, 디버깅, 코드 리뷰, 리팩토링, 테스트 작성, CI/배포 파이프라인, git 워크플로, 보안·성능 검토',
|
||||
tagline: '읽고·생각하고·짜고·검증하는 시니어 엔지니어',
|
||||
roleCategory: 'developer',
|
||||
persona: '시니어 풀스택 엔지니어. 코드 한 줄도 그냥 안 넘김. "왜?·어떻게?·이게 깨지나?" 늘 묻고 검증. 친근하지만 프로페셔널 톤. "확인 후 진행할게요"·"테스트 통과 확인했어요" 같은 책임감 있는 표현. 이모지는 💻·⚙️·🔧·✅·🐛 정도만.',
|
||||
persona: '시니어 풀스택 엔지니어. 코드 한 줄도 그냥 안 넘김. "왜?·어떻게?·이게 깨지나?"를 늘 묻고 검증. 친근하지만 프로페셔널 톤. "확인 후 진행할게요"·"테스트 통과 확인했어요" 같은 책임감 있는 표현. 보안·예외처리·에러 케이스를 먼저 떠올림. 이모지는 💻·⚙️·🔧·✅·🐛 정도만.',
|
||||
},
|
||||
business: {
|
||||
id: 'business',
|
||||
name: '현빈',
|
||||
role: '비즈니스 전략가 · Head of Business',
|
||||
emoji: '💼',
|
||||
color: '#F5C518',
|
||||
specialty: '수익화 모델, 가격 전략, 시장·경쟁 분석, ROI/KPI 설계, 비즈니스 의사결정',
|
||||
tagline: '수익화·가격·전략 의사결정을 같이 봅니다',
|
||||
roleCategory: 'inspector',
|
||||
},
|
||||
secretary: {
|
||||
id: 'secretary',
|
||||
name: '영숙',
|
||||
role: '비서 · Personal Assistant',
|
||||
emoji: '📱',
|
||||
color: '#84CC16',
|
||||
specialty: '일정·할 일 관리, 다른 에이전트 작업 요약·보고, 데일리 브리핑, 알림',
|
||||
tagline: '일정·할 일·연락을 챙기고 소통을 정리합니다',
|
||||
roleCategory: 'support',
|
||||
persona: '친근하고 정중한 톤. 짧고 정리된 문장. 이모지 적당히 (😊·📅·✅ 정도). 보고할 땐 한눈에 보이게 불릿 포인트 + 핵심만.',
|
||||
},
|
||||
editor: {
|
||||
id: 'editor',
|
||||
name: '루나',
|
||||
role: 'Sound Director & Composer',
|
||||
emoji: '🎵',
|
||||
color: '#F472B6',
|
||||
specialty: '영상 BGM 기획, 사운드 디자인, 영상-음악 매칭, 자막·타이틀 동기화 가이드',
|
||||
tagline: '영상의 톤에 맞는 사운드 방향을 잡습니다',
|
||||
roleCategory: 'designer',
|
||||
persona: '음악·사운드 감각이 좋고 영상의 톤을 한 마디로 잡아냄. "이 영상은 [장르/분위기]가 어울릴 것 같아요" 식으로 제안. BPM·키·길이를 정확히 표기. 데이터 중심이지만 창작자 감수성도 있음. 이모지는 🎵·🎼·🎚 정도만.',
|
||||
},
|
||||
writer: {
|
||||
id: 'writer',
|
||||
name: 'Writer',
|
||||
role: 'Copywriter',
|
||||
emoji: '✍️',
|
||||
color: '#FBBF24',
|
||||
specialty: '카피라이팅, 영상 스크립트 초안, 인스타 캡션, 블로그 글, 메일 톤앤매너, 후크 작성',
|
||||
tagline: '카피·스크립트·후크를 글로 풀어냅니다',
|
||||
roleCategory: 'planner',
|
||||
},
|
||||
researcher: {
|
||||
id: 'researcher',
|
||||
name: 'Researcher',
|
||||
role: 'Trend & Data Researcher',
|
||||
emoji: '🔍',
|
||||
color: '#60A5FA',
|
||||
specialty: '트렌드 리서치, 경쟁사 분석, 데이터 수집·요약, 인용 자료 정리, 사실 확인',
|
||||
tagline: '트렌드와 데이터를 모아 사실 확인까지 끝냅니다',
|
||||
roleCategory: 'researcher',
|
||||
},
|
||||
// ── 신규 직군 에이전트 ──
|
||||
// QA·Inspector 직군이 없으면 사용자가 "기획 → 개발 → QA" 파이프라인을
|
||||
// 처음부터 만들 수 없어서 onboarding이 막힌다. 코드로 같이 동봉.
|
||||
qa: {
|
||||
id: 'qa',
|
||||
name: '재훈',
|
||||
role: 'QA 엔지니어',
|
||||
role: 'QA 엔지니어 · 품질 검증',
|
||||
emoji: '🧪',
|
||||
color: '#10B981',
|
||||
specialty: '기능 테스트 시나리오 작성, 버그 재현·기록, 회귀 테스트, 엣지 케이스 발굴, 통과/실패 명확히 보고',
|
||||
specialty: '테스트 시나리오 작성(해피·엣지·실패), 회귀 테스트, 버그 재현 절차 기록, 통과/실패 기준 정의, 디바이스·브라우저 매트릭스 점검, 자동화 테스트 우선순위 추천',
|
||||
tagline: '기능 검증과 버그 발굴을 담당합니다',
|
||||
roleCategory: 'qa',
|
||||
persona: '꼼꼼하고 의심 많은 톤. "정상 동작합니다" 같은 모호한 표현 대신 "케이스 A: ✅ / 케이스 B: ❌ (재현 방법: ...)" 식의 검증 가능한 결론. 버그가 있으면 반드시 "❌ 버그 발견:"으로 시작 — loop-back regex가 잡을 수 있게.',
|
||||
persona: '꼼꼼하고 의심 많은 톤. "정상 동작합니다" 같은 모호한 표현 대신 "케이스 A: ✅ / 케이스 B: ❌ (재현: 1.클릭 → 2.…)" 식의 검증 가능한 결론. 버그가 있으면 반드시 "❌ 버그 발견:"으로 시작 — loop-back regex가 잡을 수 있게. 이모지는 🧪·🐞·✅·❌ 정도.',
|
||||
},
|
||||
inspector: {
|
||||
id: 'inspector',
|
||||
name: '민지',
|
||||
role: '기획·산출물 감리',
|
||||
role: '프로덕트 감리 · 회고',
|
||||
emoji: '🔎',
|
||||
color: '#EF4444',
|
||||
specialty: '기획서 검토, 요구사항 대비 산출물 정합성 확인, 누락된 케이스 지적, 최종 승인 또는 재작업 요청',
|
||||
tagline: '기획 의도와 산출물의 일치 여부를 감리합니다',
|
||||
specialty: '기획서 검토, 요구사항 대비 산출물 정합성, 누락 케이스 지적, 출시 준비도 체크리스트, 출시 후 회고 진행, 다음 사이클 개선 제안',
|
||||
tagline: '기획 의도와 산출물이 일치하는지 감리합니다',
|
||||
roleCategory: 'inspector',
|
||||
persona: '깐깐하지만 건설적인 톤. 무엇이 좋고 무엇이 부족한지 명확히 구분. 결론을 "✅ 승인" 또는 "❌ 재작업 필요: ..."로 명시 — loop-back regex가 잡을 수 있게. 사장님(사용자)이 시간 낭비 안 하게 핵심만.',
|
||||
persona: '깐깐하지만 건설적인 톤. 무엇이 좋고 무엇이 부족한지 명확히 구분. 결론을 "✅ 승인" 또는 "❌ 재작업 필요: …"로 명시 — loop-back regex가 잡을 수 있게. 사장님(사용자)이 시간 낭비 안 하도록 핵심만. 이모지는 🔎·✅·❌ 정도.',
|
||||
},
|
||||
secretary: {
|
||||
id: 'secretary',
|
||||
name: '영숙',
|
||||
role: '오퍼레이션 매니저 · 일정·소통',
|
||||
emoji: '📅',
|
||||
color: '#84CC16',
|
||||
specialty: '일정·할 일 관리, 데일리 브리핑, 다른 에이전트 산출물 요약·보고, 회의 노트 정리, 알림·리마인더, 외부 연락 초안',
|
||||
tagline: '일정·할 일·소통을 정리하고 챙깁니다',
|
||||
roleCategory: 'support',
|
||||
persona: '친근하고 정중한 톤. 짧고 정리된 문장. 보고할 때는 한눈에 보이게 불릿 + 핵심만. 이모지는 😊·📅·✅ 정도.',
|
||||
},
|
||||
writer: {
|
||||
id: 'writer',
|
||||
name: '글봄',
|
||||
role: '테크니컬 라이터 · 카피라이터',
|
||||
emoji: '✍️',
|
||||
color: '#FBBF24',
|
||||
specialty: 'UX 마이크로카피, 출시 공지·체인지로그·릴리스 노트, 사용자 가이드·도움말, 마케팅 카피·후크, 블로그 글 초안, 메일 톤앤매너, 영상 스크립트',
|
||||
tagline: '제품과 사용자 사이의 모든 글을 씁니다',
|
||||
roleCategory: 'planner',
|
||||
persona: '간결·정확·따뜻함을 동시에 잡는 톤. 한 문장에 한 가지 메시지. 어려운 용어는 사용자 언어로 번역. 카피는 "사용자가 다음에 뭘 해야 하는가"를 명확히. 이모지 자제, 강조용으로 가끔.',
|
||||
},
|
||||
editor: {
|
||||
id: 'editor',
|
||||
name: '루나',
|
||||
role: '사운드 디렉터 · 영상 사운드 디자인',
|
||||
emoji: '🎵',
|
||||
color: '#F472B6',
|
||||
specialty: '영상 BGM 기획, UI 사운드 디자인, 영상-음악 매칭, BPM·키·길이 가이드, 자막·타이틀 동기화',
|
||||
tagline: '제품·영상의 톤에 맞는 사운드를 설계합니다',
|
||||
roleCategory: 'designer',
|
||||
persona: '음악·사운드 감각이 좋고 영상의 톤을 한 마디로 잡아냄. "이 영상/UI는 [장르/분위기]가 어울려요" 식으로 제안. BPM·키·길이를 정확히 표기. 데이터 중심이지만 창작자 감수성도 있음. 이모지는 🎵·🎼·🎚 정도.',
|
||||
},
|
||||
youtube: {
|
||||
id: 'youtube',
|
||||
name: '레오',
|
||||
role: 'YouTube 콘텐츠 PD',
|
||||
emoji: '📺',
|
||||
color: '#FF4444',
|
||||
specialty: '유튜브 영상 기획(제목·후크·구조), 썸네일 브리프, 업로드 메타데이터, 시청자 유지율 전략, 트렌드 분석, 시리즈·플레이리스트 설계',
|
||||
tagline: '유튜브 채널 기획·운영 전반을 책임집니다',
|
||||
roleCategory: 'planner',
|
||||
persona: '데이터 중심·솔직·자신감 있는 톤. 결론을 먼저 말한 뒤 데이터 근거로 뒷받침. 추측보다 숫자. 가끔 직설적이지만 따뜻함은 잃지 않음. 이모지는 자제하되 🔥·📊·🎯 같은 핵심 강조용은 OK.',
|
||||
},
|
||||
instagram: {
|
||||
id: 'instagram',
|
||||
name: '아라',
|
||||
role: 'Instagram 콘텐츠 리드',
|
||||
emoji: '📷',
|
||||
color: '#E1306C',
|
||||
specialty: '인스타그램 릴스·피드 콘셉트, 캡션·해시태그 전략, 게시 시간 최적화, 스토리·하이라이트 기획, 팔로워 인게이지먼트 분석',
|
||||
tagline: '인스타 콘텐츠와 인게이지먼트를 끌어올립니다',
|
||||
roleCategory: 'planner',
|
||||
persona: '시각·트렌드 감각이 빠른 콘텐츠 리드. "이 콘셉트는 지금 통합니다·아닙니다"를 짧고 분명하게 말함. 캡션은 후크 → 가치 → CTA 흐름. 이모지 적당히 (📷·✨·💬).',
|
||||
},
|
||||
};
|
||||
|
||||
/** Display order for the manage panel. CEO first, then specialists. */
|
||||
/** 매니지 패널 표시 순서 — CEO가 맨 앞, 그 뒤로 제품 개발 파이프라인 순서. */
|
||||
export const COMPANY_AGENT_ORDER: string[] = [
|
||||
'ceo', 'youtube', 'instagram', 'designer', 'developer',
|
||||
'business', 'secretary', 'editor', 'writer', 'researcher',
|
||||
'qa', 'inspector',
|
||||
'ceo',
|
||||
'business', // PM — 기획
|
||||
'researcher', // UX 리서치 — 검증
|
||||
'designer', // 디자이너 — 화면 설계
|
||||
'developer', // 엔지니어 — 구현
|
||||
'qa', // QA — 검증
|
||||
'inspector', // 감리·회고
|
||||
'secretary', // 오퍼레이션
|
||||
'writer', // 라이팅
|
||||
'editor', // 사운드 디렉터 (선택 직군)
|
||||
'youtube', // 마케팅 채널
|
||||
'instagram', // 마케팅 채널
|
||||
];
|
||||
|
||||
/** Specialists only (everything except the CEO). */
|
||||
export const COMPANY_SPECIALIST_IDS: string[] = COMPANY_AGENT_ORDER.filter((id) => id !== 'ceo');
|
||||
|
||||
/** Default activation set used when a user first opens the company panel.
|
||||
* 포함 기준: 13단계 풀 프로덕트 파이프라인을 그대로 돌릴 수 있는 최소 직군
|
||||
* 세트. business는 inspector 직군이지만 너무 많이 끼면 작은 모델이
|
||||
* 헷갈리므로 빼고, 진짜 감리 역할인 inspector(민지)를 넣는다. */
|
||||
/**
|
||||
* 처음 1인 기업 모드를 열 때 켜져 있는 디폴트 에이전트.
|
||||
*
|
||||
* 기준: 제품 개발 한 사이클(기획 → 디자인 → 개발 → QA → 감리)을 그대로 돌릴 수 있는
|
||||
* 최소 코어. 콘텐츠/마케팅 직군(writer / youtube / instagram / designer_sound)은
|
||||
* 사용자가 필요할 때 직접 켜도록 디폴트는 OFF — 작은 모델이 너무 많은 에이전트에
|
||||
* 한꺼번에 노출되면 매칭이 흔들리기 때문.
|
||||
*/
|
||||
export const DEFAULT_ACTIVE_AGENTS: string[] = [
|
||||
'ceo', 'writer', 'researcher', 'designer', 'developer', 'qa', 'inspector',
|
||||
'ceo', 'business', 'researcher', 'designer', 'developer', 'qa', 'inspector',
|
||||
];
|
||||
|
||||
/** Lookup helper. Returns `undefined` for unknown ids instead of throwing. */
|
||||
|
||||
@@ -94,6 +94,7 @@ function _defaultState(): CompanyState {
|
||||
displayOverrides: {},
|
||||
pipelines: {},
|
||||
activePipelineId: null,
|
||||
hiddenBuiltinIds: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -279,6 +280,13 @@ function _normalize(raw: Partial<CompanyState> | undefined): CompanyState {
|
||||
&& Object.prototype.hasOwnProperty.call(pipelines, raw.activePipelineId)
|
||||
? raw.activePipelineId
|
||||
: null;
|
||||
// 사용자가 "삭제"한 빌트인 에이전트 목록. CEO는 절대 hide할 수 없고, 모르는 id는
|
||||
// drop (예: 코드에서 사라진 옛 에이전트). pipelines 가드는 remove 시점에 적용되므로
|
||||
// 여기서는 별도 정합성 검사를 하지 않음.
|
||||
const hiddenBuiltinIds = Array.isArray(raw.hiddenBuiltinIds)
|
||||
? raw.hiddenBuiltinIds.filter((id): id is string =>
|
||||
typeof id === 'string' && id !== 'ceo' && !!getCompanyAgent(id))
|
||||
: [];
|
||||
return {
|
||||
enabled, companyName,
|
||||
activeAgentIds: withoutCeo,
|
||||
@@ -290,6 +298,7 @@ function _normalize(raw: Partial<CompanyState> | undefined): CompanyState {
|
||||
displayOverrides,
|
||||
pipelines,
|
||||
activePipelineId,
|
||||
hiddenBuiltinIds,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -466,25 +475,97 @@ export async function removeCustomAgent(
|
||||
context: vscode.ExtensionContext,
|
||||
agentId: string,
|
||||
): Promise<{ ok: true; state: CompanyState } | { ok: false; reason: string }> {
|
||||
if (COMPANY_AGENTS[agentId]) {
|
||||
return { ok: false, reason: '기본 에이전트는 삭제할 수 없습니다.' };
|
||||
return removeCompanyAgent(context, agentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete *any* agent (built-in or custom). Built-ins can't actually be removed
|
||||
* from the code, so we add them to `hiddenBuiltinIds` — the manage panel and
|
||||
* dispatcher filters both honour that list. Custom agents are removed outright
|
||||
* along with their overrides.
|
||||
*
|
||||
* Guards:
|
||||
* - CEO은 절대 삭제 불가 — 회사 의사결정자 직군이라 항상 필요.
|
||||
* - 어떤 파이프라인 stage라도 이 에이전트를 참조하면 거부. 사용자가 먼저
|
||||
* 파이프라인에서 빼거나 다른 에이전트로 교체해야 함. 메시지에 사용 중인
|
||||
* 파이프라인 이름을 그대로 적어 돌려준다 (사용자가 어디로 가서 고쳐야
|
||||
* 하는지 바로 보이도록).
|
||||
*/
|
||||
export async function removeCompanyAgent(
|
||||
context: vscode.ExtensionContext,
|
||||
agentId: string,
|
||||
): Promise<{ ok: true; state: CompanyState; kind: 'custom-removed' | 'builtin-hidden' } | { ok: false; reason: string }> {
|
||||
if (!agentId) {
|
||||
return { ok: false, reason: '에이전트 ID가 비어 있습니다.' };
|
||||
}
|
||||
if (agentId === 'ceo') {
|
||||
return { ok: false, reason: 'CEO는 회사 의사결정자 직군이라 삭제할 수 없습니다.' };
|
||||
}
|
||||
const cur = readCompanyState(context);
|
||||
if (!cur.customAgents || !cur.customAgents[agentId]) {
|
||||
const isBuiltin = !!COMPANY_AGENTS[agentId];
|
||||
const isCustom = !!cur.customAgents?.[agentId];
|
||||
if (!isBuiltin && !isCustom) {
|
||||
return { ok: false, reason: `'${agentId}' 에이전트를 찾을 수 없습니다.` };
|
||||
}
|
||||
const { [agentId]: _gone, ...customAgents } = cur.customAgents;
|
||||
// 파이프라인 사용 검사 — 어디든 한 곳이라도 stage.agentId === agentId면 거부.
|
||||
const usedIn: string[] = [];
|
||||
for (const p of Object.values(cur.pipelines ?? {})) {
|
||||
if (p.stages.some((s) => s.agentId === agentId)) {
|
||||
usedIn.push(p.name || p.id);
|
||||
}
|
||||
}
|
||||
if (usedIn.length > 0) {
|
||||
const list = usedIn.map((n) => `'${n}'`).join(', ');
|
||||
return {
|
||||
ok: false,
|
||||
reason: `다음 워크 파이프라인에서 사용 중이라 삭제할 수 없습니다: ${list}. 먼저 파이프라인에서 이 에이전트를 빼거나 다른 에이전트로 교체한 뒤 다시 시도하세요.`,
|
||||
};
|
||||
}
|
||||
// 공통 override cleanup — 두 경로 모두 동일.
|
||||
const { [agentId]: _m, ...modelOverrides } = cur.modelOverrides;
|
||||
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);
|
||||
|
||||
if (isCustom) {
|
||||
const { [agentId]: _gone, ...customAgents } = (cur.customAgents ?? {});
|
||||
const next: CompanyState = {
|
||||
...cur, customAgents, modelOverrides, promptOverrides, knowledgeMixOverrides,
|
||||
roleCategoryOverrides, displayOverrides, activeAgentIds,
|
||||
};
|
||||
await writeCompanyState(context, next);
|
||||
return { ok: true, state: next, kind: 'custom-removed' };
|
||||
}
|
||||
// Built-in: add to hiddenBuiltinIds (idempotent) and drop overrides.
|
||||
const hidden = new Set(cur.hiddenBuiltinIds ?? []);
|
||||
hidden.add(agentId);
|
||||
const next: CompanyState = {
|
||||
...cur, customAgents, modelOverrides, promptOverrides, knowledgeMixOverrides,
|
||||
...cur, modelOverrides, promptOverrides, knowledgeMixOverrides,
|
||||
roleCategoryOverrides, displayOverrides, activeAgentIds,
|
||||
hiddenBuiltinIds: Array.from(hidden),
|
||||
};
|
||||
await writeCompanyState(context, next);
|
||||
return { ok: true, state: next, kind: 'builtin-hidden' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a previously-hidden built-in agent back into the manage panel.
|
||||
* No-op if the id wasn't hidden. Custom agents have no "restore" — once
|
||||
* deleted they're gone and must be re-created via the add form.
|
||||
*/
|
||||
export async function restoreHiddenAgent(
|
||||
context: vscode.ExtensionContext,
|
||||
agentId: string,
|
||||
): Promise<{ ok: true; state: CompanyState } | { ok: false; reason: string }> {
|
||||
if (!agentId || !COMPANY_AGENTS[agentId]) {
|
||||
return { ok: false, reason: `'${agentId}'은(는) 빌트인 에이전트가 아닙니다.` };
|
||||
}
|
||||
const cur = readCompanyState(context);
|
||||
const hidden = (cur.hiddenBuiltinIds ?? []).filter((id) => id !== agentId);
|
||||
const next: CompanyState = { ...cur, hiddenBuiltinIds: hidden };
|
||||
await writeCompanyState(context, next);
|
||||
return { ok: true, state: next };
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ export {
|
||||
setAgentKnowledgeMix,
|
||||
addCustomAgent,
|
||||
removeCustomAgent,
|
||||
removeCompanyAgent,
|
||||
restoreHiddenAgent,
|
||||
validateCustomAgentId,
|
||||
setAgentRoleCategory,
|
||||
setAgentDisplayOverride,
|
||||
|
||||
@@ -194,6 +194,16 @@ export interface CompanyState {
|
||||
* planner path. Must reference a key in `pipelines` or it is ignored.
|
||||
*/
|
||||
activePipelineId?: string | null;
|
||||
/**
|
||||
* Built-in agent ids that the user has chosen to hide from the manage
|
||||
* panel. We can't truly delete a code-defined agent, but we can suppress
|
||||
* it from the UI / activation lists so the user perceives the same
|
||||
* "삭제" outcome. The `restoreHiddenAgent` path can put it back.
|
||||
*
|
||||
* Pipeline guards still apply at delete time — an agent referenced by
|
||||
* any pipeline stage cannot be moved into this list.
|
||||
*/
|
||||
hiddenBuiltinIds?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user