refactor: Fine-tune sidebar interaction and refine company suite configuration
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user