























@@ -60,6 +60,7 @@ function resolveMaxToolResultChars(opts?: { maxToolResultChars?: number }): numb
60606161type UserAgentMessage = Extract<AgentMessage, { role: "user" }>;
6262type CompactionAppendValidator = (entryId: string, appendedText: string) => boolean;
63+type AppendMessageOptions = Parameters<SessionManager["appendMessage"]>[1];
63646465function isUserAgentMessage(message: AgentMessage): message is UserAgentMessage {
6566return message.role === "user";
@@ -643,6 +644,7 @@ export function installSessionToolResultGuard(
643644const allowSyntheticToolResults = opts?.allowSyntheticToolResults ?? true;
644645const missingToolResultText = opts?.missingToolResultText;
645646const beforeWrite = opts?.beforeMessageWriteHook;
647+const toolResultTransformerMayMutate = opts?.transformToolResultForPersistence !== undefined;
646648const redactionConfig = opts?.redactLoggingConfig;
647649const maxToolResultChars = resolveMaxToolResultChars(opts);
648650const transcriptSeqByEntryId: TranscriptSeqByEntryId = new Map();
@@ -653,9 +655,10 @@ export function installSessionToolResultGuard(
653655654656const appendMessageAndCacheTranscriptSeq = (
655657message: AgentMessage,
658+options?: AppendMessageOptions,
656659): { entryId: string; messageSeq?: number; sessionFile?: string | null } => {
657660const parentEntryId = sessionManager.getLeafId();
658-const entryId = originalAppend(message as never);
661+const entryId = originalAppend(message as never, options);
659662void opts?.onMessagePersisted?.(message);
660663const sessionFile = getSessionFile();
661664if (!sessionFile) {
@@ -686,18 +689,20 @@ export function installSessionToolResultGuard(
686689 * Run the before_message_write hook. Returns the (possibly modified) message,
687690 * or null if the message should be blocked.
688691 */
689-const applyBeforeWriteHook = (msg: AgentMessage): AgentMessage | null => {
692+const applyBeforeWriteHook = (
693+msg: AgentMessage,
694+): { message: AgentMessage; changed: boolean } | null => {
690695if (!beforeWrite) {
691-return msg;
696+return { message: msg, changed: false };
692697}
693698const result = beforeWrite({ message: msg });
694699if (result?.block) {
695700return null;
696701}
697702if (result?.message) {
698-return result.message;
703+return { message: result.message, changed: true };
699704}
700-return msg;
705+return { message: msg, changed: false };
701706};
702707703708const flushPendingToolResults = () => {
@@ -711,16 +716,20 @@ export function installSessionToolResultGuard(
711716toolName: name,
712717text: missingToolResultText,
713718});
714-const flushed = applyBeforeWriteHook(
715- persistToolResult(persistMessage(synthetic), {
716- toolCallId: id,
717- toolName: name,
718- isSynthetic: true,
719- }),
720-);
719+const persistedSynthetic = persistMessage(synthetic);
720+const transformed = persistToolResult(persistedSynthetic, {
721+toolCallId: id,
722+toolName: name,
723+isSynthetic: true,
724+});
725+const flushed = applyBeforeWriteHook(transformed);
721726if (flushed) {
722727appendMessageAndCacheTranscriptSeq(
723-capToolResultForPersistence(flushed, maxToolResultChars, redactionConfig),
728+capToolResultForPersistence(flushed.message, maxToolResultChars, redactionConfig),
729+{
730+invalidateSerializedPrefixCache:
731+persistedSynthetic !== synthetic || toolResultTransformerMayMutate || flushed.changed,
732+},
724733);
725734}
726735}
@@ -732,7 +741,8 @@ export function installSessionToolResultGuard(
732741pendingState.clear();
733742};
734743735-const guardedAppend = (message: AgentMessage) => {
744+const guardedAppend = (message: AgentMessage, callerOptions?: AppendMessageOptions) => {
745+const callerInvalidatesCache = callerOptions?.invalidateSerializedPrefixCache === true;
736746let nextMessage = message;
737747const role = (message as { role?: unknown }).role;
738748if (role === "assistant") {
@@ -758,23 +768,30 @@ export function installSessionToolResultGuard(
758768const normalizedToolResult = normalizePersistedToolResultName(nextMessage, toolName);
759769// Apply hard size cap before persistence to prevent oversized tool results
760770// from consuming the entire context window on subsequent LLM calls.
771+const persistedToolResult = persistMessage(normalizedToolResult);
761772const capped = capToolResultForPersistence(
762-persistMessage(normalizedToolResult),
773+persistedToolResult,
763774maxToolResultChars,
764775redactionConfig,
765776);
766-const persisted = applyBeforeWriteHook(
767-persistToolResult(capped, {
768-toolCallId: id ?? undefined,
769- toolName,
770-isSynthetic: false,
771-}),
772-);
777+const transformed = persistToolResult(capped, {
778+toolCallId: id ?? undefined,
779+ toolName,
780+isSynthetic: false,
781+});
782+const persisted = applyBeforeWriteHook(transformed);
773783if (!persisted) {
774784return undefined;
775785}
776786return appendMessageAndCacheTranscriptSeq(
777-capToolResultForPersistence(persisted, maxToolResultChars, redactionConfig),
787+capToolResultForPersistence(persisted.message, maxToolResultChars, redactionConfig),
788+{
789+invalidateSerializedPrefixCache:
790+callerInvalidatesCache ||
791+persistedToolResult !== normalizedToolResult ||
792+toolResultTransformerMayMutate ||
793+persisted.changed,
794+},
778795).entryId;
779796}
780797@@ -818,10 +835,12 @@ export function installSessionToolResultGuard(
818835flushPendingToolResults();
819836}
820837821-const finalMessage = applyBeforeWriteHook(persistMessage(nextMessage));
822-if (!finalMessage) {
838+const transformedMessage = persistMessage(nextMessage);
839+const finalWrite = applyBeforeWriteHook(transformedMessage);
840+if (!finalWrite) {
823841return undefined;
824842}
843+const finalMessage = finalWrite.message;
825844const finalRole = (finalMessage as { role?: unknown }).role;
826845if (
827846finalRole === "assistant" &&
@@ -845,7 +864,10 @@ export function installSessionToolResultGuard(
845864entryId: result,
846865 messageSeq,
847866 sessionFile,
848-} = appendMessageAndCacheTranscriptSeq(finalMessage);
867+} = appendMessageAndCacheTranscriptSeq(finalMessage, {
868+invalidateSerializedPrefixCache:
869+callerInvalidatesCache || transformedMessage !== nextMessage || finalWrite.changed,
870+});
849871if (sessionFile) {
850872emitSessionTranscriptUpdate({
851873 sessionFile,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。