fix(agent): prevent double-save of assistant message on abort

Track the last assistant message saved by the message_end event handler
and skip saving it again in the abort handler. This prevents the
duplicate assistant entries in session.jsonl that caused the
"tool_call_id is not found" bug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jiayuan Zhang 2026-02-15 21:31:36 +08:00
parent fa2616c390
commit 71f95d042a

View file

@ -153,6 +153,8 @@ export class Agent {
private _internalRun = false;
private _isRunning = false;
private _aborted = false;
/** Last assistant message saved by the message_end event handler */
private _lastEventSavedAssistant: AgentMessage | undefined;
private _runMutex: Promise<void> = Promise.resolve();
private _compactionPromise: Promise<void> = Promise.resolve();
private currentUserDisplayPrompt: string | undefined;
@ -681,15 +683,17 @@ export class Agent {
} finally {
// On abort, persist any partial messages that pi-agent-core appended
// via appendMessage() (no message_end event fires for those).
// Skip if message_end already fired for this message (avoids duplicates).
if (this._aborted) {
const messages = this.agent.state.messages;
const lastMsg = messages[messages.length - 1];
if (lastMsg?.role === "assistant") {
if (lastMsg?.role === "assistant" && lastMsg !== this._lastEventSavedAssistant) {
this.session.saveMessage(lastMsg);
}
}
this._isRunning = false;
this._aborted = false;
this._lastEventSavedAssistant = undefined;
this.currentUserDisplayPrompt = undefined;
this.currentUserSource = undefined;
this.runLog.flush().catch(() => {});
@ -829,6 +833,9 @@ export class Agent {
saveOptions.source = this.currentUserSource;
}
this.session.saveMessage(message, Object.keys(saveOptions).length > 0 ? saveOptions : undefined);
if (message.role === "assistant") {
this._lastEventSavedAssistant = message;
}
// Skip compaction during internal runs — internal messages will be
// rolled back from memory afterwards, so compacting now would be incorrect.
if (message.role === "assistant" && !this._internalRun) {