fix(agent): 12 项 agent 对话链路全栈修复
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
深端到端验证发现 12 个问题,6 Phase 全栈修复: Phase 5 — 快速 UX 修复: - #9: SimpleSidebar 添加新对话按钮 (SquarePen + useChatStore) - #5: 模型列表 JOIN provider_keys 过滤无 API Key 的模型 - #11: AgentOnboardingWizard 焦点领域增加 4 行业选项 (医疗健康/教育培训/金融财务/法律合规) Phase 1 — ButlerPanel 记忆修复: - #2a: MemorySection URI 从 viking://agent/.../memories/ 修正为 agent://.../ - #2b: "立即分析对话"按钮现在触发 extractAndStoreMemories Phase 2 — FTS5 中文分词: - #4: FTS5 tokenizer 从 unicode61 切换到 trigram,原生支持 CJK - 自动迁移:检测旧 unicode61 表并重建索引 - sanitize_fts_query 支持中文引号短语查询 Phase 3 — 跨会话身份持久化: - #6-8: 重新启用 USER.md 注入系统提示词 (截断前 10 行) Phase 4 — Agent 面板同步: - #1,#10: listClones 从 4 字段扩展到完整映射 (soul/userProfile 解析 nickname/emoji/userName/userRole) - updateClone 通过 identity 系统同步 nickname→SOUL.md 和 userName/userRole→USER.md Phase 6 — Agent 创建容错: - #12: createFromTemplate 增加 SaaS 不可用 fallback 验证: tsc --noEmit ✅ cargo check ✅
This commit is contained in:
@@ -56,16 +56,63 @@ export function installAgentMethods(ClientClass: { prototype: KernelClient }): v
|
||||
|
||||
/**
|
||||
* List clones — maps to listAgents() with field adaptation
|
||||
* Maps all available AgentInfo fields to Clone interface properties
|
||||
*/
|
||||
proto.listClones = async function (this: KernelClient): Promise<{ clones: any[] }> {
|
||||
const agents = await this.listAgents();
|
||||
const clones = agents.map((agent) => ({
|
||||
id: agent.id,
|
||||
name: agent.name,
|
||||
role: agent.description,
|
||||
model: agent.model,
|
||||
createdAt: new Date().toISOString(),
|
||||
}));
|
||||
const clones = agents.map((agent) => {
|
||||
// Parse personality/emoji/nickname from SOUL.md content
|
||||
const soulLines = (agent.soul || '').split('\n');
|
||||
let emoji: string | undefined;
|
||||
let personality: string | undefined;
|
||||
let nickname: string | undefined;
|
||||
for (const line of soulLines) {
|
||||
if (!emoji || !nickname) {
|
||||
// Parse header line: "> 🦞 Nickname" or "> 🦞"
|
||||
const headerMatch = line.match(/^>\s*(\p{Emoji_Presentation}|\p{Extended_Pictographic})?\s*(.+)$/u);
|
||||
if (headerMatch) {
|
||||
if (headerMatch[1] && !emoji) emoji = headerMatch[1];
|
||||
if (headerMatch[2]?.trim() && !nickname) nickname = headerMatch[2].trim();
|
||||
}
|
||||
// Also check emoji without nickname
|
||||
if (!emoji) {
|
||||
const emojiOnly = line.match(/^>\s*(\p{Emoji_Presentation}|\p{Extended_Pictographic})\s*$/u);
|
||||
if (emojiOnly) emoji = emojiOnly[1];
|
||||
}
|
||||
}
|
||||
if (!personality) {
|
||||
const match = line.match(/##\s*(?:性格|核心特质|沟通风格)/);
|
||||
if (match) personality = line.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse userName/userRole from userProfile
|
||||
let userName: string | undefined;
|
||||
let userRole: string | undefined;
|
||||
if (agent.userProfile && typeof agent.userProfile === 'object') {
|
||||
const profile = agent.userProfile as Record<string, unknown>;
|
||||
userName = profile.userName as string | undefined || profile.name as string | undefined;
|
||||
userRole = profile.userRole as string | undefined || profile.role as string | undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
id: agent.id,
|
||||
name: agent.name,
|
||||
role: agent.description,
|
||||
nickname,
|
||||
model: agent.model,
|
||||
soul: agent.soul,
|
||||
systemPrompt: agent.systemPrompt,
|
||||
temperature: agent.temperature,
|
||||
maxTokens: agent.maxTokens,
|
||||
emoji,
|
||||
personality,
|
||||
userName,
|
||||
userRole,
|
||||
createdAt: agent.createdAt || new Date().toISOString(),
|
||||
updatedAt: agent.updatedAt,
|
||||
};
|
||||
});
|
||||
return { clones };
|
||||
};
|
||||
|
||||
@@ -119,7 +166,7 @@ export function installAgentMethods(ClientClass: { prototype: KernelClient }): v
|
||||
};
|
||||
|
||||
/**
|
||||
* Update clone — maps to kernel agent_update
|
||||
* Update clone — maps to kernel agent_update + identity system for nickname/userName
|
||||
*/
|
||||
proto.updateClone = async function (this: KernelClient, id: string, updates: Record<string, unknown>): Promise<{ clone: unknown }> {
|
||||
await invoke('agent_update', {
|
||||
@@ -135,15 +182,90 @@ export function installAgentMethods(ClientClass: { prototype: KernelClient }): v
|
||||
},
|
||||
});
|
||||
|
||||
// Sync nickname/emoji to SOUL.md via identity system
|
||||
const nickname = updates.nickname as string | undefined;
|
||||
const emoji = updates.emoji as string | undefined;
|
||||
if (nickname || emoji) {
|
||||
try {
|
||||
const currentSoul = await invoke<string | null>('identity_get_file', { agentId: id, file: 'soul' });
|
||||
const soul = currentSoul || '';
|
||||
// Inject or update nickname line in SOUL.md header
|
||||
const lines = soul.split('\n');
|
||||
const headerIdx = lines.findIndex((l: string) => l.startsWith('> '));
|
||||
if (headerIdx >= 0) {
|
||||
// Update existing header line
|
||||
let header = lines[headerIdx];
|
||||
if (emoji && !header.match(/\p{Emoji_Presentation}|\p{Extended_Pictographic}/u)) {
|
||||
header = `> ${emoji} ${header.slice(2)}`;
|
||||
}
|
||||
lines[headerIdx] = header;
|
||||
} else if (emoji || nickname) {
|
||||
// Add header line after title
|
||||
const label = nickname || '';
|
||||
const icon = emoji || '';
|
||||
const titleIdx = lines.findIndex((l: string) => l.startsWith('# '));
|
||||
if (titleIdx >= 0) {
|
||||
lines.splice(titleIdx + 1, 0, `> ${icon} ${label}`.trim());
|
||||
}
|
||||
}
|
||||
await invoke('identity_update_file', { agentId: id, file: 'soul', content: lines.join('\n') });
|
||||
} catch {
|
||||
// Identity system update is non-critical
|
||||
}
|
||||
}
|
||||
|
||||
// Sync userName/userRole to USER.md via identity system
|
||||
const userName = updates.userName as string | undefined;
|
||||
const userRole = updates.userRole as string | undefined;
|
||||
if (userName || userRole) {
|
||||
try {
|
||||
const currentProfile = await invoke<string | null>('identity_get_file', { agentId: id, file: 'user_profile' });
|
||||
const profile = currentProfile || '# 用户档案\n';
|
||||
const profileLines = profile.split('\n');
|
||||
|
||||
// Update or add userName
|
||||
if (userName) {
|
||||
const nameIdx = profileLines.findIndex((l: string) => l.includes('姓名') || l.includes('userName'));
|
||||
if (nameIdx >= 0) {
|
||||
profileLines[nameIdx] = `- 姓名:${userName}`;
|
||||
} else {
|
||||
const sectionIdx = profileLines.findIndex((l: string) => l.startsWith('## 基本信息'));
|
||||
if (sectionIdx >= 0) {
|
||||
profileLines.splice(sectionIdx + 1, 0, '', `- 姓名:${userName}`);
|
||||
} else {
|
||||
profileLines.push('', '## 基本信息', '', `- 姓名:${userName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update or add userRole
|
||||
if (userRole) {
|
||||
const roleIdx = profileLines.findIndex((l: string) => l.includes('角色') || l.includes('userRole'));
|
||||
if (roleIdx >= 0) {
|
||||
profileLines[roleIdx] = `- 角色:${userRole}`;
|
||||
} else {
|
||||
profileLines.push(`- 角色:${userRole}`);
|
||||
}
|
||||
}
|
||||
|
||||
await invoke('identity_update_file', { agentId: id, file: 'user_profile', content: profileLines.join('\n') });
|
||||
} catch {
|
||||
// Identity system update is non-critical
|
||||
}
|
||||
}
|
||||
|
||||
// Return updated clone representation
|
||||
const clone = {
|
||||
id,
|
||||
name: updates.name,
|
||||
role: updates.description || updates.role,
|
||||
nickname: updates.nickname,
|
||||
model: updates.model,
|
||||
emoji: updates.emoji,
|
||||
personality: updates.personality,
|
||||
communicationStyle: updates.communicationStyle,
|
||||
systemPrompt: updates.systemPrompt,
|
||||
userName: updates.userName,
|
||||
userRole: updates.userRole,
|
||||
};
|
||||
return { clone };
|
||||
};
|
||||
|
||||
@@ -97,6 +97,27 @@ export const SCENARIO_TAGS: ScenarioTag[] = [
|
||||
icon: 'Palette',
|
||||
keywords: ['设计', 'UI', 'UX', '视觉', '原型', '界面'],
|
||||
},
|
||||
{
|
||||
id: 'healthcare',
|
||||
label: '医疗健康',
|
||||
description: '医院管理、患者服务、医疗数据分析',
|
||||
icon: 'HeartPulse',
|
||||
keywords: ['医疗', '医院', '健康', '患者', '临床', '护理', '行政'],
|
||||
},
|
||||
{
|
||||
id: 'education',
|
||||
label: '教育培训',
|
||||
description: '课程设计、教学辅助、学习规划',
|
||||
icon: 'GraduationCap',
|
||||
keywords: ['教育', '教学', '课程', '培训', '学习', '考试'],
|
||||
},
|
||||
{
|
||||
id: 'finance',
|
||||
label: '金融财务',
|
||||
description: '财务分析、风险管理、投资研究',
|
||||
icon: 'Landmark',
|
||||
keywords: ['金融', '财务', '投资', '风控', '审计', '报表'],
|
||||
},
|
||||
{
|
||||
id: 'devops',
|
||||
label: '运维部署',
|
||||
@@ -118,6 +139,13 @@ export const SCENARIO_TAGS: ScenarioTag[] = [
|
||||
icon: 'Megaphone',
|
||||
keywords: ['营销', '推广', '运营', '社媒', '增长', '转化'],
|
||||
},
|
||||
{
|
||||
id: 'legal',
|
||||
label: '法律合规',
|
||||
description: '合同审查、法规研究、合规管理',
|
||||
icon: 'Scale',
|
||||
keywords: ['法律', '合同', '合规', '法规', '审查', '风险'],
|
||||
},
|
||||
{
|
||||
id: 'other',
|
||||
label: '其他',
|
||||
|
||||
Reference in New Issue
Block a user