




















@@ -26,6 +26,7 @@ import { formatReasoningMessage } from "../agents/embedded-agent-utils.js";
2626import { resolveAgentHarnessPolicy } from "../agents/harness/policy.js";
2727import { resolveModelRefFromString, type ModelRef } from "../agents/model-selection.js";
2828import { resolvePersistedSessionRuntimeId } from "../agents/session-runtime-compat.js";
29+import { STREAM_ERROR_FALLBACK_TEXT } from "../agents/stream-message-shared.js";
2930import { DEFAULT_HEARTBEAT_FILENAME } from "../agents/workspace.js";
3031import { resolveHeartbeatReplyPayload } from "../auto-reply/heartbeat-reply-payload.js";
3132import {
@@ -839,41 +840,67 @@ function stripLeadingHeartbeatResponsePrefix(
839840return text.replace(prefixPattern, "");
840841}
841842843+type NormalizedHeartbeatDelivery = {
844+shouldSkip: boolean;
845+text: string;
846+hasMedia: boolean;
847+isInternalPlaceholderOnly: boolean;
848+};
849+850+function isStreamErrorFallbackPlaceholderOnly(text: string): boolean {
851+let remaining = text.trim();
852+if (!remaining) {
853+return false;
854+}
855+856+while (remaining.startsWith(STREAM_ERROR_FALLBACK_TEXT)) {
857+remaining = remaining.slice(STREAM_ERROR_FALLBACK_TEXT.length).trimStart();
858+}
859+return remaining.length === 0;
860+}
861+842862function normalizeHeartbeatReply(
843863payload: ReplyPayload,
844864responsePrefix: string | undefined,
845865ackMaxChars: number,
846-) {
866+): NormalizedHeartbeatDelivery {
847867const rawText = typeof payload.text === "string" ? payload.text : "";
848868const textForStrip = stripLeadingHeartbeatResponsePrefix(rawText, responsePrefix);
849869const stripped = stripHeartbeatToken(textForStrip, {
850870mode: "heartbeat",
851871maxAckChars: ackMaxChars,
852872});
853873const hasMedia = resolveSendableOutboundReplyParts(payload).hasMedia;
854-if (stripped.shouldSkip && !hasMedia) {
874+const isInternalPlaceholderOnly = isStreamErrorFallbackPlaceholderOnly(stripped.text);
875+if ((stripped.shouldSkip || isInternalPlaceholderOnly) && !hasMedia) {
855876return {
856877shouldSkip: true,
857878text: "",
858879 hasMedia,
880+ isInternalPlaceholderOnly,
859881};
860882}
861-let finalText = stripped.text;
883+let finalText = isInternalPlaceholderOnly ? "" : stripped.text;
862884if (responsePrefix && finalText && !finalText.startsWith(responsePrefix)) {
863885finalText = `${responsePrefix} ${finalText}`;
864886}
865-return { shouldSkip: false, text: finalText, hasMedia };
887+return { shouldSkip: false, text: finalText, hasMedia, isInternalPlaceholderOnly };
866888}
867889868890function normalizeHeartbeatToolNotification(
869891response: HeartbeatToolResponse,
870892responsePrefix: string | undefined,
871-) {
893+): NormalizedHeartbeatDelivery {
872894let finalText = getHeartbeatToolNotificationText(response);
873895if (responsePrefix && finalText && !finalText.startsWith(responsePrefix)) {
874896finalText = `${responsePrefix} ${finalText}`;
875897}
876-return { shouldSkip: false, text: finalText, hasMedia: false };
898+return {
899+shouldSkip: false,
900+text: finalText,
901+hasMedia: false,
902+isInternalPlaceholderOnly: false,
903+};
877904}
878905879906type HeartbeatWakePayloadFlags = {
@@ -1927,7 +1954,12 @@ export async function runHeartbeatOnce(opts: {
19271954 ? normalizeHeartbeatToolNotification(heartbeatToolResponse, responsePrefix)
19281955 : replyPayload
19291956 ? normalizeHeartbeatReply(replyPayload, responsePrefix, ackMaxChars)
1930- : { shouldSkip: true, text: "", hasMedia: false };
1957+ : {
1958+shouldSkip: true,
1959+text: "",
1960+hasMedia: false,
1961+isInternalPlaceholderOnly: false,
1962+};
19311963// For exec completion events, don't skip even if the response looks like HEARTBEAT_OK.
19321964// The model should be responding with exec results, not ack tokens.
19331965// Also, if normalized.text is empty due to token stripping but we have exec completion,
@@ -1936,6 +1968,7 @@ export async function runHeartbeatOnce(opts: {
19361968!heartbeatToolResponse &&
19371969hasRelayableExecCompletion &&
19381970!normalized.text.trim() &&
1971+!normalized.isInternalPlaceholderOnly &&
19391972replyPayload?.text?.trim()
19401973 ? replyPayload.text.trim()
19411974 : null;
@@ -1952,7 +1985,9 @@ export async function runHeartbeatOnce(opts: {
19521985normalized.shouldSkip = false;
19531986}
19541987const shouldSkipMain =
1955-normalized.shouldSkip && !normalized.hasMedia && !hasRelayableExecCompletion;
1988+normalized.shouldSkip &&
1989+!normalized.hasMedia &&
1990+(!hasRelayableExecCompletion || normalized.isInternalPlaceholderOnly);
19561991if (shouldSkipMain && reasoningPayloads.length === 0) {
19571992await restoreHeartbeatUpdatedAt({
19581993 storePath,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。