multica/src/agent/profile/index.ts
Jiang Bohan a80c858ce5 feat(agent): add profile name and user content management
Add methods to ProfileManager, Agent, and AsyncAgent for:
- Getting/setting agent display name (stored in config.json)
- Getting/setting user.md content

This enables the desktop app to manage agent settings.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 13:26:40 +08:00

255 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Agent Profile 模块
*
* 管理 agent 的身份、人格、记忆等配置
*/
import type { AgentProfile, CreateProfileOptions, ProfileConfig, ProfileManagerOptions } from "./types.js";
import type { ToolsConfig } from "../tools/policy.js";
import { DEFAULT_TEMPLATES } from "./templates.js";
import {
ensureProfileDir,
getProfileDir,
loadProfile,
profileExists,
saveProfile,
writeProfileConfig,
writeProfileFile,
} from "./storage.js";
import { PROFILE_FILES } from "./types.js";
export { type AgentProfile, type CreateProfileOptions, type ProfileConfig, type ProfileManagerOptions } from "./types.js";
export { DEFAULT_TEMPLATES } from "./templates.js";
export { getProfileDir, profileExists } from "./storage.js";
/**
* 创建新的 Agent Profile
*
* @param profileId - Profile ID
* @param options - 创建选项
* @returns 创建的 AgentProfile
*/
export function createAgentProfile(
profileId: string,
options?: CreateProfileOptions,
): AgentProfile {
const { baseDir, useTemplates = true } = options ?? {};
// 确保目录存在
ensureProfileDir(profileId, { baseDir });
// 创建 profile
const profile: AgentProfile = {
id: profileId,
};
// 如果使用模板,填充默认内容
if (useTemplates) {
profile.soul = DEFAULT_TEMPLATES.soul;
profile.user = DEFAULT_TEMPLATES.user;
profile.workspace = DEFAULT_TEMPLATES.workspace;
profile.memory = DEFAULT_TEMPLATES.memory;
// 保存到文件
saveProfile(profile, { baseDir });
}
return profile;
}
/**
* 加载 Agent Profile
*
* @param profileId - Profile ID
* @param options - 加载选项
* @returns AgentProfile如果不存在返回 undefined
*/
export function loadAgentProfile(
profileId: string,
options?: { baseDir?: string | undefined },
): AgentProfile | undefined {
if (!profileExists(profileId, options)) {
return undefined;
}
return loadProfile(profileId, options);
}
/**
* 加载或创建 Agent Profile
*
* @param profileId - Profile ID
* @param options - 选项
* @returns AgentProfile
*/
export function getOrCreateAgentProfile(
profileId: string,
options?: CreateProfileOptions,
): AgentProfile {
const existing = loadAgentProfile(profileId, options);
if (existing) {
return existing;
}
return createAgentProfile(profileId, options);
}
/**
* Profile Manager - 用于管理单个 profile 的类
*/
export class ProfileManager {
private readonly profileId: string;
private readonly baseDir: string | undefined;
private profile: AgentProfile | undefined;
constructor(options: ProfileManagerOptions) {
this.profileId = options.profileId;
this.baseDir = options.baseDir;
}
/** 获取 profile如果未加载则加载 */
getProfile(): AgentProfile | undefined {
if (!this.profile) {
this.profile = loadAgentProfile(this.profileId, { baseDir: this.baseDir });
}
return this.profile;
}
/** 获取或创建 profile */
getOrCreateProfile(useTemplates = true): AgentProfile {
if (!this.profile) {
this.profile = getOrCreateAgentProfile(this.profileId, {
baseDir: this.baseDir,
useTemplates,
});
}
return this.profile;
}
/** 获取 profile 目录路径 */
getProfileDir(): string {
return getProfileDir(this.profileId, { baseDir: this.baseDir });
}
/** 构建 system prompt */
buildSystemPrompt(): string {
const profile = this.getProfile();
if (!profile) {
return "";
}
const parts: string[] = [];
if (profile.soul) {
parts.push(profile.soul);
}
if (profile.user) {
parts.push(profile.user);
}
if (profile.workspace) {
parts.push(profile.workspace);
}
if (profile.memory) {
parts.push(profile.memory);
}
// 注入 profile 目录路径,让 Agent 知道文件在哪里
const profileDir = this.getProfileDir();
parts.push(`## Profile Directory\n\nYour profile files are located at: \`${profileDir}\`\n\nUse \`edit\` or \`write\` tools to update these files when needed.`);
return parts.join("\n\n");
}
/** 获取 tools 配置 */
getToolsConfig(): ToolsConfig | undefined {
const profile = this.getProfile();
return profile?.config?.tools;
}
/** 获取完整的 profile config */
getProfileConfig(): ProfileConfig | undefined {
const profile = this.getProfile();
return profile?.config;
}
/** 更新 tools 配置 */
updateToolsConfig(toolsConfig: ToolsConfig): void {
const profile = this.getOrCreateProfile(false);
const currentConfig = profile.config ?? {};
const newConfig: ProfileConfig = {
...currentConfig,
tools: toolsConfig,
};
profile.config = newConfig;
this.profile = profile;
saveProfile({ id: this.profileId, config: newConfig }, { baseDir: this.baseDir });
}
/** 设置单个 tool 的启用状态 */
setToolEnabled(toolName: string, enabled: boolean): ToolsConfig {
const currentConfig = this.getToolsConfig() ?? {};
const allow = new Set(currentConfig.allow ?? []);
const deny = new Set(currentConfig.deny ?? []);
if (enabled) {
// Enable: add to allow, remove from deny
allow.add(toolName);
deny.delete(toolName);
} else {
// Disable: add to deny, remove from allow
deny.add(toolName);
allow.delete(toolName);
}
// Build new config object, only including non-empty arrays
const newConfig: ToolsConfig = { ...currentConfig };
if (allow.size > 0) {
newConfig.allow = Array.from(allow);
} else {
delete newConfig.allow;
}
if (deny.size > 0) {
newConfig.deny = Array.from(deny);
} else {
delete newConfig.deny;
}
this.updateToolsConfig(newConfig);
return newConfig;
}
/** 获取 Agent 名称 */
getName(): string | undefined {
const profile = this.getProfile();
return profile?.config?.name;
}
/** 更新 Agent 名称 */
updateName(name: string): void {
const profile = this.getOrCreateProfile(false);
const currentConfig = profile.config ?? {};
const newConfig: ProfileConfig = {
...currentConfig,
name,
};
profile.config = newConfig;
this.profile = profile;
writeProfileConfig(this.profileId, newConfig, { baseDir: this.baseDir });
}
/** 获取 user.md 内容 */
getUserContent(): string | undefined {
const profile = this.getProfile();
return profile?.user;
}
/** 更新 user.md 内容 */
updateUserContent(content: string): void {
writeProfileFile(this.profileId, PROFILE_FILES.user, content, { baseDir: this.baseDir });
// Update cached profile
if (this.profile) {
this.profile.user = content;
}
}
}