feat(FN-1065): add immediate message response mode for heartbeats

- Add MessageResponseMode and messageResponseMode runtime config to core agent heartbeat types and exports
- Extend MessageStore with an onMessageToAgent hook plus runtime hook updates for agent-directed messages
- Wire HeartbeatMonitor to wake agents on incoming messages when messageResponseMode is immediate and state is eligible
- Update AgentDetailView with a Message Response Mode setting, validation, and runtimeConfig persistence
- Expand core and engine tests to cover hook behavior, wake-on-message triggering, and start/stop hook lifecycle
This commit is contained in:
gsxdsm
2026-04-07 18:43:16 -07:00
parent 06fe36a64c
commit 981c5d951c
7 changed files with 365 additions and 5 deletions

View File

@@ -1175,6 +1175,9 @@ function ConfigTab({
if (rc.heartbeatTimeoutMs !== undefined && rc.heartbeatTimeoutMs !== null) {
initial.heartbeatTimeoutMs = String(rc.heartbeatTimeoutMs);
}
if (rc.messageResponseMode === "immediate" || rc.messageResponseMode === "on-heartbeat") {
initial.messageResponseMode = rc.messageResponseMode;
}
return initial;
});
@@ -1199,7 +1202,7 @@ function ConfigTab({
}
// Check heartbeat values
const rc = agent.runtimeConfig ?? {};
for (const key of ["heartbeatIntervalMs", "heartbeatTimeoutMs"] as const) {
for (const key of ["heartbeatIntervalMs", "heartbeatTimeoutMs", "messageResponseMode"] as const) {
const current = heartbeatValues[key]?.trim() ?? "";
const persisted = rc[key] !== undefined && rc[key] !== null ? String(rc[key]) : "";
if (current !== persisted) return true;
@@ -1259,6 +1262,11 @@ function ConfigTab({
}
}
const messageResponseModeForValidation = heartbeatValues.messageResponseMode?.trim();
if (messageResponseModeForValidation && !["immediate", "on-heartbeat"].includes(messageResponseModeForValidation)) {
validationErrors.messageResponseMode = "\"Message Response Mode\" must be either immediate or on-heartbeat";
}
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
addToast("Please fix validation errors before saving", "error");
@@ -1290,6 +1298,13 @@ function ConfigTab({
}
}
const messageResponseMode = heartbeatValues.messageResponseMode?.trim();
if (!messageResponseMode) {
delete newRuntimeConfig.messageResponseMode;
} else {
newRuntimeConfig.messageResponseMode = messageResponseMode;
}
setIsSaving(true);
try {
await updateAgent(agent.id, { metadata: newMetadata, runtimeConfig: newRuntimeConfig }, projectId);
@@ -1404,6 +1419,25 @@ function ConfigTab({
<span className="config-hint">Time without heartbeat before agent is considered unresponsive. Leave empty for system default (60000ms)</span>
)}
</div>
<div className="config-field">
<label htmlFor="hb-messageResponseMode">Message Response Mode</label>
<select
id="hb-messageResponseMode"
className={cn("select", !!errors.messageResponseMode && "input--error")}
value={heartbeatValues.messageResponseMode ?? ""}
onChange={(e) => handleHeartbeatFieldChange("messageResponseMode", e.target.value)}
>
<option value="">System Default (On Heartbeat)</option>
<option value="on-heartbeat">On Heartbeat</option>
<option value="immediate">Immediate</option>
</select>
{errors.messageResponseMode ? (
<span className="config-error">{errors.messageResponseMode}</span>
) : (
<span className="config-hint">How this agent responds to incoming messages. &apos;Immediate&apos; wakes the agent as soon as a message arrives. &apos;On Heartbeat&apos; defers processing to the next scheduled heartbeat.</span>
)}
</div>
</div>
</div>