feat(FN-1049): add per-agent heartbeat configuration via runtimeConfig

- Define AgentHeartbeatConfig interface in core types (heartbeatIntervalMs, heartbeatTimeoutMs, maxConcurrentRuns)
- Update HeartbeatMonitor to resolve per-agent config from AgentStore with validated min/max clamping
- Wire AgentStore into HeartbeatMonitor via InProcessRuntime initialization
- Add heartbeat settings section to dashboard AgentDetailView ConfigTab
- Add PATCH /api/agents/:id endpoint accepting runtimeConfig updates
- Add comprehensive tests for per-agent heartbeat config resolution and validation
- Document per-agent heartbeat configuration in AGENTS.md
This commit is contained in:
gsxdsm
2026-04-07 11:50:17 -07:00
parent 612827eee8
commit acae0e7aa0
10 changed files with 579 additions and 40 deletions

View File

@@ -10,7 +10,7 @@
*/
import { mkdir, readFile, writeFile, readdir, unlink } from "node:fs/promises";
import { existsSync } from "node:fs";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { randomUUID } from "node:crypto";
import { EventEmitter } from "node:events";
@@ -732,6 +732,21 @@ export class AgentStore extends EventEmitter {
return JSON.parse(content) as AgentData;
}
/**
* Synchronously read an agent from disk (for use in synchronous hot paths).
* Returns null if the agent file does not exist or cannot be parsed.
* @param agentId - The agent ID
*/
getCachedAgent(agentId: string): Agent | null {
try {
const path = join(this.agentsDir, `${agentId}.json`);
const content = readFileSync(path, "utf-8");
return this.parseAgent(JSON.parse(content) as AgentData);
} catch {
return null;
}
}
private parseAgent(data: AgentData): Agent {
return {
id: data.id,

View File

@@ -1,5 +1,5 @@
export { COLUMNS, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES } from "./types.js";
export type { Column, IssueInfo, IssueState, PrInfo, PrStatus, Task, TaskAttachment, TaskComment, TaskCommentInput, TaskCreateInput, TaskDetail, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, MergeDetails, MergeResult, Settings, GlobalSettings, ProjectSettings, SettingsScope, TaskStep, StepStatus, TaskLogEntry, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentCapability, AgentHeartbeatEvent, AgentHeartbeatRun, HeartbeatInvocationSource, AgentTaskSession, AgentStats, NtfyNotificationEvent, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, Mailbox } from "./types.js";
export type { Column, IssueInfo, IssueState, PrInfo, PrStatus, Task, TaskAttachment, TaskComment, TaskCommentInput, TaskCreateInput, TaskDetail, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, MergeDetails, MergeResult, Settings, GlobalSettings, ProjectSettings, SettingsScope, TaskStep, StepStatus, TaskLogEntry, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentCapability, AgentHeartbeatConfig, AgentHeartbeatEvent, AgentHeartbeatRun, HeartbeatInvocationSource, AgentTaskSession, AgentStats, NtfyNotificationEvent, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, Mailbox } from "./types.js";
export { AGENT_VALID_TRANSITIONS } from "./types.js";
export { AgentStore } from "./agent-store.js";
export type { AgentStoreEvents } from "./agent-store.js";

View File

@@ -1455,7 +1455,7 @@ export interface Agent {
icon?: string;
/** Agent ID this agent reports to (org hierarchy) */
reportsTo?: string;
/** Runtime configuration (maxTurns, thinkingLevel, etc.) */
/** Runtime configuration. Supports: AgentHeartbeatConfig keys (heartbeatIntervalMs, heartbeatTimeoutMs, maxConcurrentRuns) */
runtimeConfig?: Record<string, unknown>;
/** Why the agent was paused (error, manual, etc.) */
pauseReason?: string;
@@ -1469,6 +1469,16 @@ export interface Agent {
lastError?: string;
}
/** Per-agent heartbeat configuration, stored in agent.runtimeConfig */
export interface AgentHeartbeatConfig {
/** Polling interval in ms (default: 30000). Min: 1000 */
heartbeatIntervalMs?: number;
/** Heartbeat timeout in ms (default: 60000). Min: 5000 */
heartbeatTimeoutMs?: number;
/** Max concurrent heartbeat runs per agent (default: 1). Min: 1 */
maxConcurrentRuns?: number;
}
/** Extended agent information including heartbeat history */
export interface AgentDetail extends Agent {
/** Recent heartbeat events (last N events) */