-
Notifications
You must be signed in to change notification settings - Fork 149
fix(agent-core): recover interrupted tool exchanges #273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| "@moonshot-ai/agent-core": patch | ||
| "@moonshot-ai/kimi-code": patch | ||
| --- | ||
|
|
||
| Recover resumed sessions that were interrupted after recording a tool call but before recording its tool result. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,8 @@ const TOOL_EMPTY_STATUS = '<system>Tool output is empty.</system>'; | |
| const TOOL_EMPTY_ERROR_STATUS = | ||
| '<system>ERROR: Tool execution failed. Tool output is empty.</system>'; | ||
| const TOOL_OUTPUT_EMPTY_TEXT = 'Tool output is empty.'; | ||
| const INTERRUPTED_TOOL_RESULT = | ||
| 'Kimi Code was interrupted before this tool call could record a result. Treat this tool call as failed and continue from the latest user instruction.'; | ||
|
|
||
| export class ContextMemory { | ||
| private _history: ContextMessage[] = []; | ||
|
|
@@ -206,6 +208,27 @@ export class ContextMemory { | |
| this.pushHistory(message); | ||
| } | ||
|
|
||
| recoverInterruptedToolExchanges(): number { | ||
| // A sealed step can intentionally keep waiting for async tool output across | ||
| // context operations. An open step at replay EOF means the process stopped | ||
| // before the loop could finish pairing recorded tool calls. | ||
| const missingToolResultIds = this.openSteps.size > 0 ? [...this.pendingToolResultIds] : []; | ||
| for (const toolCallId of missingToolResultIds) { | ||
| this.appendLoopEvent({ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the first resume repairs an interrupted tool exchange, this call runs after Useful? React with 👍 / 👎. |
||
| type: 'tool.result', | ||
| parentUuid: toolCallId, | ||
| toolCallId, | ||
| result: { | ||
| output: INTERRUPTED_TOOL_RESULT, | ||
| isError: true, | ||
| }, | ||
| }); | ||
| } | ||
| this.openSteps.clear(); | ||
| this.flushDeferredMessagesIfToolExchangeClosed(); | ||
| return missingToolResultIds.length; | ||
| } | ||
|
|
||
| private flushDeferredMessagesIfToolExchangeClosed(): void { | ||
| if (this.pendingToolResultIds.size > 0 || this.deferredMessages.length === 0) { | ||
| return; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If manual or auto compaction completes after a
tool.callis recorded but before itstool.result,context.applyCompactionpreserves the assistant/tool-call message but clearsopenSteps; after a crash at that point, replay reaches EOF withpendingToolResultIdsstill populated andopenSteps.size === 0, so this guard synthesizes no result. The next model request still contains the compacted history with an assistant tool call and no matching tool message, reproducing the provider 400 this recovery is meant to prevent.Useful? React with 👍 / 👎.