feat(hooks): capture reasoning and assistant messages in hooks (#719)

This commit is contained in:
Cameron
2026-01-28 16:28:23 -08:00
committed by GitHub
parent 4c702057e0
commit 4794361b50
6 changed files with 113 additions and 0 deletions

View File

@@ -2669,6 +2669,17 @@ export default function App({
lastDequeuedMessageRef.current = null; // Clear - message was processed successfully
lastSentInputRef.current = null; // Clear - no recovery needed
// Get last assistant message and reasoning for Stop hook
const lastAssistant = Array.from(
buffersRef.current.byId.values(),
).findLast((item) => item.kind === "assistant" && "text" in item);
const assistantMessage =
lastAssistant && "text" in lastAssistant
? lastAssistant.text
: undefined;
const precedingReasoning = buffersRef.current.lastReasoning;
buffersRef.current.lastReasoning = undefined; // Clear after use
// Run Stop hooks - if blocked/errored, continue the conversation with feedback
const stopHookResult = await runStopHooks(
stopReasonToHandle,
@@ -2676,6 +2687,9 @@ export default function App({
Array.from(buffersRef.current.byId.values()).filter(
(item) => item.kind === "tool_call",
).length,
undefined, // workingDirectory (uses default)
precedingReasoning,
assistantMessage,
);
// If hook blocked (exit 2), inject stderr feedback and continue conversation

View File

@@ -181,6 +181,8 @@ export type Buffers = {
interrupted?: boolean; // Track if stream was interrupted by user (skip stale refreshes)
commitGeneration?: number; // Incremented when resuming from error to invalidate pending refreshes
abortGeneration?: number; // Incremented on each interrupt to detect cancellation across async boundaries
lastReasoning?: string; // Track last reasoning content for hooks (PostToolUse, Stop)
lastAssistantMessage?: string; // Track last assistant message for hooks (PostToolUse)
usage: {
promptTokens: number;
completionTokens: number;
@@ -243,6 +245,15 @@ function markAsFinished(b: Buffers, id: string) {
const updatedLine = { ...line, phase: "finished" as const };
b.byId.set(id, updatedLine);
// console.log(`[MARK_FINISHED] Successfully marked ${id} as finished`);
// Track last reasoning content for hooks (PostToolUse and Stop will include it)
if (line.kind === "reasoning" && "text" in line && line.text) {
b.lastReasoning = line.text;
}
// Track last assistant message for hooks (PostToolUse will include it)
if (line.kind === "assistant" && "text" in line && line.text) {
b.lastAssistantMessage = line.text;
}
} else {
// console.log(`[MARK_FINISHED] Did NOT mark ${id} as finished (conditions not met)`);
}
@@ -717,6 +728,12 @@ export function onChunk(b: Buffers, chunk: LettaStreamingResponse) {
// Args parsing failed
}
// Get and clear preceding reasoning/message for hook
const precedingReasoning = b.lastReasoning;
const precedingAssistantMessage = b.lastAssistantMessage;
b.lastReasoning = undefined;
b.lastAssistantMessage = undefined;
runPostToolUseHooks(
serverToolInfo.toolName,
parsedArgs,
@@ -727,6 +744,8 @@ export function onChunk(b: Buffers, chunk: LettaStreamingResponse) {
toolCallId,
undefined,
b.agentId,
precedingReasoning,
precedingAssistantMessage,
).catch(() => {});
b.serverToolCalls.delete(toolCallId);