feat(FN-3588): add immediate wake controls for agent inbox and message API

The merge delivers four major bodies of work. The dominant theme is FN-3588, which adds an "immediate wake" override to the message inbox API, exposes it in the MailboxModal UI, and updates `agent-heartbeat.ts` and `executor.ts` to honor the override alongside timer/signal triggers. FN-3705 gates re

Fusion-Task-Id: FN-3588
This commit is contained in:
Fusion
2026-05-07 10:41:11 -07:00
committed by gsxdsm
parent af82119a75
commit 0efba0890f
10 changed files with 358 additions and 26 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useCallback } from "react";
import { useState, useCallback, useMemo } from "react";
import { X, Send, Loader2, Bot, AlertCircle } from "lucide-react";
import type { ParticipantType, MessageType } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
@@ -47,6 +47,11 @@ export function MessageComposer({
const [isSending, setIsSending] = useState(false);
const [error, setError] = useState<string | null>(null);
const selectedAgent = useMemo(() => agents.find((agent) => agent.id === toId), [agents, toId]);
const recipientIsAgent = toType === "agent";
const recipientAlwaysImmediate = recipientIsAgent && selectedAgent?.runtimeConfig?.messageResponseMode === "immediate";
const wakeImmediately = recipientIsAgent && (wakeRecipient || recipientAlwaysImmediate);
const isValid = toId.trim() !== "" && content.trim().length > 0 && content.length <= MAX_CONTENT_LENGTH;
const handleSend = useCallback(async () => {
@@ -57,14 +62,11 @@ export function MessageComposer({
try {
const messageType: MessageType = toType === "agent" ? "user-to-agent" : "system";
const includeWake = wakeRecipient && toType === "agent";
const metadata =
replyContext || includeWake
? {
...(replyContext ? { replyTo: { messageId: replyContext.messageId } } : {}),
...(includeWake ? { wakeRecipient: true } : {}),
}
replyContext
? { replyTo: { messageId: replyContext.messageId } }
: undefined;
const sendWakeImmediately = wakeImmediately;
await sendMessage(
{
toId: toId.trim(),
@@ -72,6 +74,7 @@ export function MessageComposer({
content: content.trim(),
type: messageType,
...(metadata ? { metadata } : {}),
...(sendWakeImmediately ? { wakeImmediately: true } : {}),
},
projectId,
);
@@ -83,7 +86,7 @@ export function MessageComposer({
} finally {
setIsSending(false);
}
}, [isValid, isSending, toId, toType, content, wakeRecipient, replyContext, projectId, onSend, addToast]);
}, [isValid, isSending, toId, toType, content, wakeImmediately, replyContext, projectId, onSend, addToast]);
const handleAgentSelect = useCallback((agentId: string) => {
setToId(agentId);
@@ -174,19 +177,22 @@ export function MessageComposer({
</div>
{/* Wake recipient toggle (agents only) */}
{toType === "agent" && (
{recipientIsAgent && (
<div className="message-composer-field message-composer-field--wake">
<label className="message-composer-wake-label">
<input
type="checkbox"
checked={wakeRecipient}
checked={wakeImmediately}
disabled={recipientAlwaysImmediate}
onChange={(e) => setWakeRecipient(e.target.checked)}
data-testid="message-composer-wake"
/>
<span>
Wake recipient immediately
<span className="message-composer-wake-hint">
(overrides their messageResponseMode)
Wake agent immediately
<span className="message-composer-wake-hint" data-testid="message-composer-wake-hint">
{recipientAlwaysImmediate
? "(agent is already set to immediate response mode)"
: "(one-off override for this message only)"}
</span>
</span>
</label>