Fix job continuation: setMessages never fires sendAutomaticallyWhen

Gate 1 found the teacher staying silent after a research brief landed.
The SDK only evaluates sendAutomaticallyWhen inside addToolOutput — an
external setMessages (our syncFromServer) updates the UI but never
triggers the continuation. Fix: syncFromServer checks
lastAssistantMessageIsCompleteWithToolCalls and calls sendMessage()
(itself a valid continuation trigger); plus a mount-continuation effect
for the reload case (job fulfilled while no client was open).
This commit is contained in:
Amos
2026-08-22 09:58:41 +03:00
parent c27f7f2f76
commit 34dee919bb
+32 -2
View File
@@ -81,6 +81,25 @@ export function Chat({
}
}, [initialPrompt, initialMessages.length, sendMessage]);
// Mount continuation: if the stored transcript ends with a fulfilled tool
// (a job result landed while no client was open), resume the lesson — the
// SDK's sendAutomaticallyWhen only fires inside addToolOutput, so reloads
// need this nudge. Once per mount; the turn that follows ends in text (or
// a pending question), so no loop.
const mountContinuation = useRef(false);
useEffect(() => {
if (mountContinuation.current) return;
mountContinuation.current = true;
if (
status === "ready" &&
messages.length > 0 &&
lastAssistantMessageIsCompleteWithToolCalls({ messages })
) {
void sendMessage();
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- mount-only by design
}, []);
const pending = findPendingToolCall(messages);
const busy = status === "submitted" || status === "streaming";
@@ -102,8 +121,19 @@ export function Chat({
const res = await fetch(`/api/chats/${chatId}`);
if (!res.ok) return;
const record = (await res.json()) as { messages?: TeacherUIMessage[] };
if (Array.isArray(record.messages)) setMessages(record.messages);
}, [chatId, setMessages]);
if (!Array.isArray(record.messages)) return;
setMessages(record.messages);
// setMessages does NOT fire sendAutomaticallyWhen (the SDK only checks
// it inside addToolOutput) — without this the teacher would stay silent
// after a job result lands. sendMessage() with no args re-requests with
// the current list: the continuation. (contracts.ts protocol §4.)
if (
status === "ready" &&
lastAssistantMessageIsCompleteWithToolCalls({ messages: record.messages })
) {
void sendMessage();
}
}, [chatId, setMessages, sendMessage, status]);
const onSend = () => {
const t = text.trim();