This commit is contained in:
Helder Santos 2026-01-29 19:00:20 +00:00 committed by GitHub
commit 215d2f9db5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 46 additions and 2 deletions

View File

@ -48,6 +48,8 @@ export type AgentRunLoopResult =
autoCompactionCompleted: boolean; autoCompactionCompleted: boolean;
/** Payload keys sent directly (not via pipeline) during tool flush. */ /** Payload keys sent directly (not via pipeline) during tool flush. */
directlySentBlockKeys?: Set<string>; directlySentBlockKeys?: Set<string>;
/** Tool call IDs that were executed successfully (phase === "result" without error). */
executedToolCallIds?: Set<string>;
} }
| { kind: "final"; payload: ReplyPayload }; | { kind: "final"; payload: ReplyPayload };
@ -82,6 +84,8 @@ export async function runAgentTurnWithFallback(params: {
let autoCompactionCompleted = false; let autoCompactionCompleted = false;
// Track payloads sent directly (not via pipeline) during tool flush to avoid duplicates. // Track payloads sent directly (not via pipeline) during tool flush to avoid duplicates.
const directlySentBlockKeys = new Set<string>(); const directlySentBlockKeys = new Set<string>();
// Track tool calls that were executed successfully (completed with result phase).
const executedToolCallIds = new Set<string>();
const runId = params.opts?.runId ?? crypto.randomUUID(); const runId = params.opts?.runId ?? crypto.randomUUID();
params.opts?.onAgentRunStart?.(runId); params.opts?.onAgentRunStart?.(runId);
@ -307,6 +311,14 @@ export async function runAgentTurnWithFallback(params: {
if (phase === "start" || phase === "update") { if (phase === "start" || phase === "update") {
await params.typingSignals.signalToolStart(); await params.typingSignals.signalToolStart();
} }
// Track successful tool execution (result phase without error).
if (phase === "result") {
const toolCallId = typeof evt.data.toolCallId === "string" ? evt.data.toolCallId : "";
const isError = Boolean(evt.data.isError);
if (toolCallId && !isError) {
executedToolCallIds.add(toolCallId);
}
}
} }
// Track auto-compaction completion // Track auto-compaction completion
if (evt.stream === "compaction") { if (evt.stream === "compaction") {
@ -554,5 +566,6 @@ export async function runAgentTurnWithFallback(params: {
didLogHeartbeatStrip, didLogHeartbeatStrip,
autoCompactionCompleted, autoCompactionCompleted,
directlySentBlockKeys: directlySentBlockKeys.size > 0 ? directlySentBlockKeys : undefined, directlySentBlockKeys: directlySentBlockKeys.size > 0 ? directlySentBlockKeys : undefined,
executedToolCallIds: executedToolCallIds.size > 0 ? executedToolCallIds : undefined,
}; };
} }

View File

@ -328,7 +328,13 @@ export async function runReplyAgent(params: {
return finalizeWithFollowup(runOutcome.payload, queueKey, runFollowupTurn); return finalizeWithFollowup(runOutcome.payload, queueKey, runFollowupTurn);
} }
const { runResult, fallbackProvider, fallbackModel, directlySentBlockKeys } = runOutcome; const {
runResult,
fallbackProvider,
fallbackModel,
directlySentBlockKeys,
executedToolCallIds,
} = runOutcome;
let { didLogHeartbeatStrip, autoCompactionCompleted } = runOutcome; let { didLogHeartbeatStrip, autoCompactionCompleted } = runOutcome;
if ( if (
@ -391,8 +397,33 @@ export async function runReplyAgent(params: {
// Drain any late tool/block deliveries before deciding there's "nothing to send". // Drain any late tool/block deliveries before deciding there's "nothing to send".
// Otherwise, a late typing trigger (e.g. from a tool callback) can outlive the run and // Otherwise, a late typing trigger (e.g. from a tool callback) can outlive the run and
// keep the typing indicator stuck. // keep the typing indicator stuck.
if (payloadArray.length === 0) if (payloadArray.length === 0) {
// If tools were executed but no final response was generated, send a confirmation.
// Skip if verbose is enabled (onToolResult already sent notifications via tool callbacks)
// or if it's a heartbeat (which should be silent).
const hasExecutedTools =
executedToolCallIds && executedToolCallIds.size > 0;
const verboseEnabled = shouldEmitToolResult();
// Only send confirmation if:
// 1. Tools were executed successfully
// 2. Verbose is disabled (onToolResult wasn't called, so no notifications were sent)
// 3. Not a heartbeat (heartbeats should be silent)
// 4. All pending tool tasks completed (to avoid race conditions)
if (
hasExecutedTools &&
!verboseEnabled &&
!isHeartbeat &&
pendingToolTasks.size === 0
) {
const confirmationPayload: ReplyPayload = {
text: "✅ Concluído",
};
const taggedConfirmation = applyReplyToMode(confirmationPayload);
await signalTypingIfNeeded([taggedConfirmation], typingSignals);
return finalizeWithFollowup(taggedConfirmation, queueKey, runFollowupTurn);
}
return finalizeWithFollowup(undefined, queueKey, runFollowupTurn); return finalizeWithFollowup(undefined, queueKey, runFollowupTurn);
}
const payloadResult = buildReplyPayloads({ const payloadResult = buildReplyPayloads({
payloads: payloadArray, payloads: payloadArray,