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

深端到端验证发现 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:
iven
2026-04-16 09:21:46 +08:00
parent 08af78aa83
commit 3c01754c40
10 changed files with 330 additions and 44 deletions

View File

@@ -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 };
};

View File

@@ -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: '其他',