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:
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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) */
|
||||
|
||||
Reference in New Issue
Block a user