feat(FN-1058): add heartbeat execution with agent tool support

- Extract shared agent tools (bash, read, write, edit) from executor into agent-tools.ts for reuse
- Implement heartbeat execution in HeartbeatMonitor with configurable interval and task_done support
- Wire heartbeat execution into InProcessRuntime lifecycle (start/stop)
- Add comprehensive test suite covering execution, timeouts, concurrency, and error handling
- Add heartbeat-related log constants to logger
This commit is contained in:
gsxdsm
2026-04-07 13:02:05 -07:00
parent 4e220d0287
commit 6f91753270
6 changed files with 997 additions and 66 deletions

View File

@@ -4,6 +4,8 @@ import type {
Task,
CentralCore,
AgentStore,
HeartbeatInvocationSource,
AgentHeartbeatRun,
} from "@fusion/core";
import { Scheduler } from "../scheduler.js";
import { TaskExecutor, type TaskExecutorOptions } from "../executor.js";
@@ -214,6 +216,8 @@ export class InProcessRuntime
this.heartbeatMonitor = new HeartbeatMonitor({
store: this.agentStore,
agentStore: this.agentStore, // enables per-agent config resolution
taskStore: this.taskStore,
rootDir: this.config.workingDirectory,
onMissed: (agentId) => {
runtimeLog.warn(`Agent ${agentId} missed heartbeat`);
},
@@ -389,6 +393,47 @@ export class InProcessRuntime
};
}
/**
* Get the HeartbeatMonitor instance (if initialized).
* Returns undefined when agent monitoring is not available.
*/
getHeartbeatMonitor(): HeartbeatMonitor | undefined {
return this.heartbeatMonitor;
}
/**
* Execute a heartbeat run for an agent.
*
* Delegates to HeartbeatMonitor.executeHeartbeat().
* Throws if the runtime is not active or the heartbeat monitor is not initialized.
*
* @param agentId - The agent ID to execute a heartbeat for
* @param source - What triggered this heartbeat
* @param options - Optional task ID override and trigger detail
* @returns The completed heartbeat run
*/
async executeHeartbeat(
agentId: string,
source: HeartbeatInvocationSource,
options?: { taskId?: string; triggerDetail?: string }
): Promise<AgentHeartbeatRun | null> {
if (this.status !== "active") {
throw new Error(`Cannot execute heartbeat: runtime status is ${this.status}`);
}
if (!this.heartbeatMonitor) {
return null;
}
runtimeLog.log(`Executing heartbeat for agent ${agentId} (source=${source})`);
const result = await this.heartbeatMonitor.executeHeartbeat({
agentId,
source,
...options,
});
runtimeLog.log(`Heartbeat completed for agent ${agentId}`);
return result;
}
/**
* Set the StuckTaskDetector for this runtime.
*/