feat(agent): add profile system and improve tools
- Add Agent Profile module for managing agent identity, soul, tools, memory, and bootstrap configuration - Add profile CLI (pnpm agent:profile) for creating/listing/showing profiles - Default sessionId to UUIDv7 instead of "default" - Expose Agent.sessionId as public readonly property - Improve exec/process tools error handling (no more crashes on spawn errors) - Add 'output' action to process tool for reading stdout/stderr - Better tool descriptions to guide agent in choosing exec vs process Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
46a6cb3061
commit
200b2cefda
15 changed files with 740 additions and 58 deletions
155
src/agent/profile/index.ts
Normal file
155
src/agent/profile/index.ts
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* Agent Profile 模块
|
||||
*
|
||||
* 管理 agent 的身份、人格、记忆等配置
|
||||
*/
|
||||
|
||||
import type { AgentProfile, CreateProfileOptions, ProfileManagerOptions } from "./types.js";
|
||||
import { DEFAULT_TEMPLATES } from "./templates.js";
|
||||
import {
|
||||
ensureProfileDir,
|
||||
getProfileDir,
|
||||
loadProfile,
|
||||
profileExists,
|
||||
saveProfile,
|
||||
} from "./storage.js";
|
||||
|
||||
export { type AgentProfile, type CreateProfileOptions, 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.identity = DEFAULT_TEMPLATES.identity;
|
||||
profile.tools = DEFAULT_TEMPLATES.tools;
|
||||
profile.memory = DEFAULT_TEMPLATES.memory;
|
||||
profile.bootstrap = DEFAULT_TEMPLATES.bootstrap;
|
||||
|
||||
// 保存到文件
|
||||
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;
|
||||
}
|
||||
|
||||
/** 构建 system prompt */
|
||||
buildSystemPrompt(): string {
|
||||
const profile = this.getProfile();
|
||||
if (!profile) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (profile.identity) {
|
||||
parts.push(profile.identity);
|
||||
}
|
||||
|
||||
if (profile.soul) {
|
||||
parts.push(profile.soul);
|
||||
}
|
||||
|
||||
if (profile.tools) {
|
||||
parts.push(profile.tools);
|
||||
}
|
||||
|
||||
if (profile.memory) {
|
||||
parts.push(profile.memory);
|
||||
}
|
||||
|
||||
if (profile.bootstrap) {
|
||||
parts.push(profile.bootstrap);
|
||||
}
|
||||
|
||||
return parts.join("\n\n");
|
||||
}
|
||||
}
|
||||
94
src/agent/profile/storage.ts
Normal file
94
src/agent/profile/storage.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Agent Profile 文件存储
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { PROFILE_FILES, type AgentProfile } from "./types.js";
|
||||
|
||||
const DEFAULT_BASE_DIR = join(homedir(), ".super-multica", "agent-profiles");
|
||||
|
||||
export interface StorageOptions {
|
||||
baseDir?: string | undefined;
|
||||
}
|
||||
|
||||
/** 获取 profile 目录路径 */
|
||||
export function getProfileDir(profileId: string, options?: StorageOptions): string {
|
||||
const baseDir = options?.baseDir ?? DEFAULT_BASE_DIR;
|
||||
return join(baseDir, profileId);
|
||||
}
|
||||
|
||||
/** 确保 profile 目录存在 */
|
||||
export function ensureProfileDir(profileId: string, options?: StorageOptions): string {
|
||||
const dir = getProfileDir(profileId, options);
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
/** 检查 profile 是否存在 */
|
||||
export function profileExists(profileId: string, options?: StorageOptions): boolean {
|
||||
const dir = getProfileDir(profileId, options);
|
||||
return existsSync(dir);
|
||||
}
|
||||
|
||||
/** 读取单个 profile 文件 */
|
||||
export function readProfileFile(
|
||||
profileId: string,
|
||||
fileName: string,
|
||||
options?: StorageOptions,
|
||||
): string | undefined {
|
||||
const dir = getProfileDir(profileId, options);
|
||||
const filePath = join(dir, fileName);
|
||||
if (!existsSync(filePath)) {
|
||||
return undefined;
|
||||
}
|
||||
return readFileSync(filePath, "utf-8");
|
||||
}
|
||||
|
||||
/** 写入单个 profile 文件 */
|
||||
export function writeProfileFile(
|
||||
profileId: string,
|
||||
fileName: string,
|
||||
content: string,
|
||||
options?: StorageOptions,
|
||||
): void {
|
||||
const dir = ensureProfileDir(profileId, options);
|
||||
const filePath = join(dir, fileName);
|
||||
writeFileSync(filePath, content, "utf-8");
|
||||
}
|
||||
|
||||
/** 加载完整的 AgentProfile */
|
||||
export function loadProfile(profileId: string, options?: StorageOptions): AgentProfile {
|
||||
return {
|
||||
id: profileId,
|
||||
soul: readProfileFile(profileId, PROFILE_FILES.soul, options),
|
||||
identity: readProfileFile(profileId, PROFILE_FILES.identity, options),
|
||||
tools: readProfileFile(profileId, PROFILE_FILES.tools, options),
|
||||
memory: readProfileFile(profileId, PROFILE_FILES.memory, options),
|
||||
bootstrap: readProfileFile(profileId, PROFILE_FILES.bootstrap, options),
|
||||
};
|
||||
}
|
||||
|
||||
/** 保存 AgentProfile(只写入非空字段) */
|
||||
export function saveProfile(profile: AgentProfile, options?: StorageOptions): void {
|
||||
const { id, soul, identity, tools, memory, bootstrap } = profile;
|
||||
|
||||
if (soul !== undefined) {
|
||||
writeProfileFile(id, PROFILE_FILES.soul, soul, options);
|
||||
}
|
||||
if (identity !== undefined) {
|
||||
writeProfileFile(id, PROFILE_FILES.identity, identity, options);
|
||||
}
|
||||
if (tools !== undefined) {
|
||||
writeProfileFile(id, PROFILE_FILES.tools, tools, options);
|
||||
}
|
||||
if (memory !== undefined) {
|
||||
writeProfileFile(id, PROFILE_FILES.memory, memory, options);
|
||||
}
|
||||
if (bootstrap !== undefined) {
|
||||
writeProfileFile(id, PROFILE_FILES.bootstrap, bootstrap, options);
|
||||
}
|
||||
}
|
||||
40
src/agent/profile/templates.ts
Normal file
40
src/agent/profile/templates.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Agent Profile 默认模板
|
||||
*/
|
||||
|
||||
export const DEFAULT_TEMPLATES = {
|
||||
soul: `# Soul
|
||||
|
||||
You are a helpful AI assistant. Follow these guidelines:
|
||||
|
||||
- Be concise and direct in your responses
|
||||
- Ask clarifying questions when requirements are ambiguous
|
||||
- Admit when you don't know something
|
||||
- Focus on solving the user's actual problem
|
||||
`,
|
||||
|
||||
identity: `# Identity
|
||||
|
||||
- Name: Assistant
|
||||
- Role: General-purpose AI assistant
|
||||
`,
|
||||
|
||||
tools: `# Tools
|
||||
|
||||
Use the available tools effectively:
|
||||
|
||||
- **exec**: Run shell commands. Always check the working directory first.
|
||||
- **read/write/edit**: File operations. Prefer edit over write for existing files.
|
||||
- **process**: Manage long-running background processes.
|
||||
`,
|
||||
|
||||
memory: `# Memory
|
||||
|
||||
(Persistent knowledge will be stored here)
|
||||
`,
|
||||
|
||||
bootstrap: `# Bootstrap
|
||||
|
||||
You are starting a new conversation. Review the context and be ready to assist.
|
||||
`,
|
||||
} as const;
|
||||
44
src/agent/profile/types.ts
Normal file
44
src/agent/profile/types.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* Agent Profile 类型定义
|
||||
*/
|
||||
|
||||
/** Profile 文件名常量 */
|
||||
export const PROFILE_FILES = {
|
||||
soul: "soul.md",
|
||||
identity: "identity.md",
|
||||
tools: "tools.md",
|
||||
memory: "memory.md",
|
||||
bootstrap: "bootstrap.md",
|
||||
} as const;
|
||||
|
||||
/** Agent Profile 配置 */
|
||||
export interface AgentProfile {
|
||||
/** Profile ID */
|
||||
id: string;
|
||||
/** 人格约束 - 定义 agent 的行为边界和风格 */
|
||||
soul?: string | undefined;
|
||||
/** 身份信息 - agent 的名称和自我认知 */
|
||||
identity?: string | undefined;
|
||||
/** 自定义工具描述 - 额外的工具使用说明 */
|
||||
tools?: string | undefined;
|
||||
/** 持久记忆 - 长期知识库 */
|
||||
memory?: string | undefined;
|
||||
/** 初始上下文 - 每次对话的引导信息 */
|
||||
bootstrap?: string | undefined;
|
||||
}
|
||||
|
||||
/** Profile Manager 选项 */
|
||||
export interface ProfileManagerOptions {
|
||||
/** Profile ID */
|
||||
profileId: string;
|
||||
/** 基础目录,默认 ~/.super-multica/agent-profiles */
|
||||
baseDir?: string | undefined;
|
||||
}
|
||||
|
||||
/** 创建 Profile 的选项 */
|
||||
export interface CreateProfileOptions {
|
||||
/** 基础目录 */
|
||||
baseDir?: string | undefined;
|
||||
/** 是否使用默认模板初始化 */
|
||||
useTemplates?: boolean | undefined;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue