feat(FN-4255): complete Step 4 — add docs and release note
Fusion-Task-Id: FN-4255 Fusion-Task-Lineage: 315c5491-0e27-46cb-8474-fda9bb3a0eff
This commit is contained in:
5
.changeset/fn-4255-direct-report-staleness.md
Normal file
5
.changeset/fn-4255-direct-report-staleness.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Fixes direct-report health classification in heartbeat report summaries to use each report's configured heartbeat interval instead of heartbeat timeout budget. Reports are now marked stale only when heartbeat age exceeds `max(heartbeatIntervalMs × 4, 5 minutes)`, matching the dashboard health semantics and preventing false stale flags for agents still within their scheduled cadence.
|
||||
@@ -781,6 +781,8 @@ When the bound task is `executor-class` or `blocked`, the default procedure dire
|
||||
|
||||
The manager-facing reports health block in that prompt is populated from `AgentStore.getAgentsByReportsTo(agent.id)`. Engine code must call that store method with its `AgentStore` instance binding intact because some implementations resolve direct reports through `this.listAgents()`. If the section disappears unexpectedly, look for logs like `Failed to load reports ... Cannot read properties of undefined (reading 'listAgents')`, which indicate an unbound method call regressed.
|
||||
|
||||
Direct-report staleness in this reports-health block uses each report's configured heartbeat interval, with threshold `max(heartbeatIntervalMs × 4, 5 minutes)`. This matches dashboard health classification semantics and avoids timeout-budget-based false stale flags.
|
||||
|
||||
This behavior is inherited by new non-ephemeral agents because agent creation seeds a per-agent `HEARTBEAT.md` file from the built-in default. If an agent sets `heartbeatProcedurePath`, that markdown file fully replaces the built-in default at runtime for task-scoped heartbeats. No-task heartbeats always fall back to the ambient built-in procedure so the prompt never references task-only tools.
|
||||
|
||||
For pre-existing agents, use `POST /api/agents/:id/upgrade-heartbeat-procedure` (also exposed as **Upgrade to Default Heartbeat Procedure** in the agent detail Config tab) to re-seed from the current built-in constant. When the built-in default changes, running this upgrade propagates the new default to existing agents; direct operator edits to an agent’s existing procedure file are preserved unless this upgrade is run (the upgrade overwrites the per-agent file).
|
||||
|
||||
@@ -227,7 +227,7 @@ describe("executeHeartbeat", () => {
|
||||
vi.mocked(store.getCachedAgent).mockImplementation((id: string) => ({
|
||||
id,
|
||||
runtimeConfig: { heartbeatIntervalMs: 60 * 60_000 },
|
||||
}) as Agent);
|
||||
}) as unknown as Agent);
|
||||
vi.mocked(store.getAgentsByReportsTo).mockResolvedValue([
|
||||
{ id: "agent-frontend", name: "Frontend Engineer", state: "active", taskId: "FN-201", lastHeartbeatAt: new Date(now - 53 * 60_000).toISOString(), updatedAt: new Date(now - 53 * 60_000).toISOString() } as Agent,
|
||||
{ id: "agent-writer", name: "Technical Writer", state: "active", taskId: "FN-202", lastHeartbeatAt: new Date(now - 51 * 60_000).toISOString(), updatedAt: new Date(now - 51 * 60_000).toISOString() } as Agent,
|
||||
@@ -250,7 +250,7 @@ describe("executeHeartbeat", () => {
|
||||
vi.mocked(store.getCachedAgent).mockImplementation((id: string) => ({
|
||||
id,
|
||||
runtimeConfig: { heartbeatIntervalMs: 60 * 60_000 },
|
||||
}) as Agent);
|
||||
}) as unknown as Agent);
|
||||
vi.mocked(store.getAgentsByReportsTo).mockResolvedValue([
|
||||
{
|
||||
id: "agent-overdue-a",
|
||||
@@ -282,13 +282,13 @@ describe("executeHeartbeat", () => {
|
||||
const store = createStoreWithAgentForExec();
|
||||
vi.mocked(store.getCachedAgent).mockImplementation((id: string) => {
|
||||
if (id === "agent-short") {
|
||||
return { id, runtimeConfig: { heartbeatIntervalMs: 5 * 60_000 } } as Agent;
|
||||
return { id, runtimeConfig: { heartbeatIntervalMs: 5 * 60_000 } } as unknown as Agent;
|
||||
}
|
||||
if (id === "agent-medium") {
|
||||
return { id, runtimeConfig: { heartbeatIntervalMs: 60 * 60_000 } } as Agent;
|
||||
return { id, runtimeConfig: { heartbeatIntervalMs: 60 * 60_000 } } as unknown as Agent;
|
||||
}
|
||||
if (id === "agent-long") {
|
||||
return { id, runtimeConfig: { heartbeatIntervalMs: 4 * 60 * 60_000 } } as Agent;
|
||||
return { id, runtimeConfig: { heartbeatIntervalMs: 4 * 60 * 60_000 } } as unknown as Agent;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
@@ -311,7 +311,7 @@ describe("executeHeartbeat", () => {
|
||||
vi.mocked(store.getCachedAgent).mockImplementation((id: string) => ({
|
||||
id,
|
||||
runtimeConfig: { heartbeatIntervalMs: 1_000 },
|
||||
}) as Agent);
|
||||
}) as unknown as Agent);
|
||||
vi.mocked(store.getAgentsByReportsTo).mockResolvedValue([
|
||||
{ id: "agent-fast", name: "Fast Poller", state: "active", taskId: "FN-210", lastHeartbeatAt: new Date(now - 2 * 60_000).toISOString(), updatedAt: new Date(now - 2 * 60_000).toISOString() } as Agent,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user