fix(coding-agent): mark retrying agent end events
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed AgentSession retry, compaction, and event settlement to use the awaited agent lifecycle instead of a separate event queue.
|
- Fixed AgentSession retry, compaction, and event settlement to use the awaited agent lifecycle instead of a separate event queue, and added `willRetry` to `agent_end` session events.
|
||||||
- Fixed the subagent extension's parallel mode to return useful per-task output and failed-task diagnostics to the parent model instead of 100-character previews ([#4710](https://github.com/earendil-works/pi/issues/4710)).
|
- Fixed the subagent extension's parallel mode to return useful per-task output and failed-task diagnostics to the parent model instead of 100-character previews ([#4710](https://github.com/earendil-works/pi/issues/4710)).
|
||||||
- Fixed Windows local bash execution to hide helper console windows when launched from background SDK processes ([#4699](https://github.com/earendil-works/pi/issues/4699)).
|
- Fixed Windows local bash execution to hide helper console windows when launched from background SDK processes ([#4699](https://github.com/earendil-works/pi/issues/4699)).
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,12 @@ export function parseSkillBlock(text: string): ParsedSkillBlock | null {
|
|||||||
|
|
||||||
/** Session-specific events that extend the core AgentEvent */
|
/** Session-specific events that extend the core AgentEvent */
|
||||||
export type AgentSessionEvent =
|
export type AgentSessionEvent =
|
||||||
| AgentEvent
|
| Exclude<AgentEvent, { type: "agent_end" }>
|
||||||
|
| {
|
||||||
|
type: "agent_end";
|
||||||
|
messages: AgentMessage[];
|
||||||
|
willRetry: boolean;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: "queue_update";
|
type: "queue_update";
|
||||||
steering: readonly string[];
|
steering: readonly string[];
|
||||||
@@ -485,7 +490,7 @@ export class AgentSession {
|
|||||||
await this._emitExtensionEvent(event);
|
await this._emitExtensionEvent(event);
|
||||||
|
|
||||||
// Notify all listeners
|
// Notify all listeners
|
||||||
this._emit(event);
|
this._emit(event.type === "agent_end" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event) } : event);
|
||||||
|
|
||||||
// Handle session persistence
|
// Handle session persistence
|
||||||
if (event.type === "message_end") {
|
if (event.type === "message_end") {
|
||||||
@@ -531,6 +536,21 @@ export class AgentSession {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private _willRetryAfterAgentEnd(event: Extract<AgentEvent, { type: "agent_end" }>): boolean {
|
||||||
|
const settings = this.settingsManager.getRetrySettings();
|
||||||
|
if (!settings.enabled || this._retryAttempt >= settings.maxRetries) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = event.messages.length - 1; i >= 0; i--) {
|
||||||
|
const message = event.messages[i];
|
||||||
|
if (message.role === "assistant") {
|
||||||
|
return this._isRetryableError(message as AssistantMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/** Extract text content from a message */
|
/** Extract text content from a message */
|
||||||
private _getUserMessageText(message: Message): string {
|
private _getUserMessageText(message: Message): string {
|
||||||
if (message.role !== "user") return "";
|
if (message.role !== "user") return "";
|
||||||
|
|||||||
@@ -113,14 +113,13 @@ describe("AgentSession auto-compaction queue resume", () => {
|
|||||||
|
|
||||||
const runAutoCompaction = (
|
const runAutoCompaction = (
|
||||||
session as unknown as {
|
session as unknown as {
|
||||||
_runAutoCompaction: (reason: "overflow" | "threshold", willRetry: boolean) => Promise<void>;
|
_runAutoCompaction: (reason: "overflow" | "threshold", willRetry: boolean) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
)._runAutoCompaction.bind(session);
|
)._runAutoCompaction.bind(session);
|
||||||
|
|
||||||
await runAutoCompaction("threshold", false);
|
await expect(runAutoCompaction("threshold", false)).resolves.toBe(true);
|
||||||
await vi.advanceTimersByTimeAsync(100);
|
|
||||||
|
|
||||||
expect(continueSpy).toHaveBeenCalledTimes(1);
|
expect(continueSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not compact repeatedly after overflow recovery already attempted", async () => {
|
it("should not compact repeatedly after overflow recovery already attempted", async () => {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ describe("AgentSession retry and event characterization", () => {
|
|||||||
await harness.session.prompt("test");
|
await harness.session.prompt("test");
|
||||||
|
|
||||||
expect(retryEvents).toEqual(["start:1", "end:true"]);
|
expect(retryEvents).toEqual(["start:1", "end:true"]);
|
||||||
|
expect(harness.eventsOfType("agent_end").map((event) => event.willRetry)).toEqual([true, false]);
|
||||||
expect(harness.faux.state.callCount).toBe(2);
|
expect(harness.faux.state.callCount).toBe(2);
|
||||||
expect(harness.session.isRetrying).toBe(false);
|
expect(harness.session.isRetrying).toBe(false);
|
||||||
});
|
});
|
||||||
@@ -90,6 +91,7 @@ describe("AgentSession retry and event characterization", () => {
|
|||||||
await harness.session.prompt("test");
|
await harness.session.prompt("test");
|
||||||
|
|
||||||
expect(retryEvents).toEqual(["start:1", "start:2", "end:false"]);
|
expect(retryEvents).toEqual(["start:1", "start:2", "end:false"]);
|
||||||
|
expect(harness.eventsOfType("agent_end").map((event) => event.willRetry)).toEqual([true, true, false]);
|
||||||
expect(harness.faux.state.callCount).toBe(3);
|
expect(harness.faux.state.callCount).toBe(3);
|
||||||
expect(harness.session.isRetrying).toBe(false);
|
expect(harness.session.isRetrying).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user