feat(heartbeat): add heartbeat core runner and profile integration

This commit is contained in:
Jiang Bohan 2026-02-06 16:41:56 +08:00
parent 91aa433f34
commit ffa5355a95
19 changed files with 934 additions and 2 deletions

View file

@ -50,6 +50,7 @@ export function createAgentProfile(
profile.user = DEFAULT_TEMPLATES.user;
profile.workspace = DEFAULT_TEMPLATES.workspace;
profile.memory = DEFAULT_TEMPLATES.memory;
profile.heartbeat = DEFAULT_TEMPLATES.heartbeat;
// 保存到文件
saveProfile(profile, { baseDir });
@ -150,6 +151,7 @@ export class ProfileManager {
user: profile.user,
workspace: profile.workspace,
memory: profile.memory,
heartbeat: profile.heartbeat,
config: profile.config,
},
profileDir: this.getProfileDir(),
@ -168,6 +170,19 @@ export class ProfileManager {
return profile?.config;
}
/** Get heartbeat configuration from profile config */
getHeartbeatConfig():
| {
enabled?: boolean | undefined;
every?: string | undefined;
prompt?: string | undefined;
ackMaxChars?: number | undefined;
}
| undefined {
const profile = this.getProfile();
return profile?.config?.heartbeat;
}
/** 更新 tools 配置 */
updateToolsConfig(toolsConfig: ToolsConfig): void {
const profile = this.getOrCreateProfile(false);

View file

@ -95,13 +95,14 @@ export function loadProfile(profileId: string, options?: StorageOptions): AgentP
user: readProfileFile(profileId, PROFILE_FILES.user, options),
workspace: readProfileFile(profileId, PROFILE_FILES.workspace, options),
memory: readProfileFile(profileId, PROFILE_FILES.memory, options),
heartbeat: readProfileFile(profileId, PROFILE_FILES.heartbeat, options),
config: readProfileConfig(profileId, options),
};
}
/** 保存 AgentProfile只写入非空字段 */
export function saveProfile(profile: AgentProfile, options?: StorageOptions): void {
const { id, soul, user, workspace, memory, config } = profile;
const { id, soul, user, workspace, memory, heartbeat, config } = profile;
if (soul !== undefined) {
writeProfileFile(id, PROFILE_FILES.soul, soul, options);
@ -115,6 +116,9 @@ export function saveProfile(profile: AgentProfile, options?: StorageOptions): vo
if (memory !== undefined) {
writeProfileFile(id, PROFILE_FILES.memory, memory, options);
}
if (heartbeat !== undefined) {
writeProfileFile(id, PROFILE_FILES.heartbeat, heartbeat, options);
}
if (config !== undefined) {
writeProfileConfig(id, config, options);
}

View file

@ -72,6 +72,7 @@ Your profile directory contains these files (use \`edit\` or \`write\` to update
| \`user.md\` | About your human | As you learn about them |
| \`workspace.md\` | This file — your rules | When you discover better conventions |
| \`memory.md\` | Long-term knowledge | Regularly — capture what matters |
| \`heartbeat.md\` | Background check instructions | When heartbeat behavior should change |
## Every Session
@ -89,6 +90,7 @@ You wake up fresh each session. These files are your continuity:
- **Long-term:** \`MEMORY.md\` — your curated memories, lessons learned
- **Daily notes:** \`memory/YYYY-MM-DD.md\` — raw logs of what happened (optional)
- **Heartbeat:** \`heartbeat.md\` — periodic check loop instructions
Capture what matters. Decisions, context, things to remember.
@ -101,6 +103,7 @@ Capture what matters. Decisions, context, things to remember.
- \`memory.md\` — Your learnings: decisions made, lessons learned, important context
- \`workspace.md\` — Your rules: conventions, workflows, how you should operate
- \`soul.md\` — Your identity: only change if user wants to reshape who you are
- \`heartbeat.md\` — Periodic background checks and alert rules
**Rules:**
- **DO NOT** say "I'll remember that" without ACTUALLY calling \`edit\` or \`write\` on a file
@ -148,5 +151,11 @@ _(Persistent knowledge will be stored here. Update this as you learn.)_
## Lessons Learned
## Important Context
`,
heartbeat: `# heartbeat.md
# Keep this file empty (or with only comments) to skip heartbeat API calls.
# Add tasks below when you want the agent to check something periodically.
`,
} as const;

View file

@ -11,6 +11,7 @@ export const PROFILE_FILES = {
user: "user.md",
workspace: "workspace.md",
memory: "memory.md",
heartbeat: "heartbeat.md",
config: "config.json",
} as const;
@ -42,6 +43,17 @@ export interface ProfileConfig {
reasoningMode?: "off" | "on" | "stream" | undefined;
/** Exec approval configuration (security level, ask mode, allowlist) */
execApproval?: ExecApprovalConfig | undefined;
/** Heartbeat configuration */
heartbeat?: {
/** Global heartbeat enable switch */
enabled?: boolean | undefined;
/** Interval, e.g. "30m", "1h" */
every?: string | undefined;
/** Optional prompt override */
prompt?: string | undefined;
/** Max chars after HEARTBEAT_OK to still treat as ack */
ackMaxChars?: number | undefined;
} | undefined;
}
/** Agent Profile configuration */
@ -56,6 +68,8 @@ export interface AgentProfile {
workspace?: string | undefined;
/** Persistent memory - long-term knowledge base */
memory?: string | undefined;
/** Periodic heartbeat instructions */
heartbeat?: string | undefined;
/** Profile configuration (from config.json) */
config?: ProfileConfig | undefined;
}