mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(coding-agent): cancel compaction on session abort
Include compaction and branch summarization in session idle tracking so abort waits for cancellation to settle.\n\nfixes #8920
This commit is contained in:
parent
4e69b0c280
commit
bea67d90d1
5 changed files with 70 additions and 27 deletions
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
- Fixed the write tool reporting UTF-16 code-unit counts as byte counts by removing the misleading count ([#8979](https://github.com/earendil-works/pi/issues/8979)).
|
||||
- Fixed proxied plain-HTTP provider requests hanging after a tool call by tunneling them with CONNECT ([#8134](https://github.com/earendil-works/pi/issues/8134)).
|
||||
- Fixed RPC `abort` reporting success without cancelling an in-progress manual compaction ([#8920](https://github.com/earendil-works/pi/issues/8920)).
|
||||
|
||||
## [0.84.4] - 2026-08-28
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ See [set_follow_up_mode](#set_follow_up_mode) for controlling how follow-up mess
|
|||
|
||||
#### abort
|
||||
|
||||
Abort the current agent operation.
|
||||
Abort the current operation and wait for the session to become idle before responding.
|
||||
|
||||
```json
|
||||
{"type": "abort"}
|
||||
|
|
|
|||
|
|
@ -617,7 +617,7 @@ export class AgentSession {
|
|||
}
|
||||
|
||||
private _resolveIdleWaitIfIdle(): void {
|
||||
if (this._isAgentRunActive || !this._resolveIdleWait) {
|
||||
if (!this.isIdle || !this._resolveIdleWait) {
|
||||
return;
|
||||
}
|
||||
const resolve = this._resolveIdleWait;
|
||||
|
|
@ -921,9 +921,9 @@ export class AgentSession {
|
|||
return this._isAgentRunActive;
|
||||
}
|
||||
|
||||
/** Whether the session has no active agent run, retry, auto-compaction, or queued continuation. */
|
||||
/** Whether the session has no active agent run, compaction, branch summary, retry, or queued continuation. */
|
||||
get isIdle(): boolean {
|
||||
return !this._isAgentRunActive;
|
||||
return !this._isAgentRunActive && !this.isCompacting;
|
||||
}
|
||||
|
||||
/** Current effective system prompt (includes any per-turn extension modifications) */
|
||||
|
|
@ -1618,6 +1618,8 @@ export class AgentSession {
|
|||
*/
|
||||
async abort(): Promise<void> {
|
||||
this.abortRetry();
|
||||
this.abortCompaction();
|
||||
this.abortBranchSummary();
|
||||
this.agent.abort();
|
||||
await this.waitForIdle();
|
||||
}
|
||||
|
|
@ -1921,6 +1923,11 @@ export class AgentSession {
|
|||
);
|
||||
}
|
||||
|
||||
private _clearManualCompactionState(): void {
|
||||
this._compactionAbortController = undefined;
|
||||
this._resolveIdleWaitIfIdle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually compact the session context.
|
||||
*
|
||||
|
|
@ -2051,7 +2058,7 @@ export class AgentSession {
|
|||
details,
|
||||
};
|
||||
// compaction_end listeners may submit queued prompts, so expose idle state before notifying them.
|
||||
this._compactionAbortController = undefined;
|
||||
this._clearManualCompactionState();
|
||||
this._emit({
|
||||
type: "compaction_end",
|
||||
reason: "manual",
|
||||
|
|
@ -2064,7 +2071,7 @@ export class AgentSession {
|
|||
const message = error instanceof Error ? error.message : String(error);
|
||||
const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
|
||||
const errorMessage = aborted ? undefined : `Compaction failed: ${message}`;
|
||||
this._compactionAbortController = undefined;
|
||||
this._clearManualCompactionState();
|
||||
this._emit({
|
||||
type: "compaction_end",
|
||||
reason: "manual",
|
||||
|
|
@ -2082,7 +2089,7 @@ export class AgentSession {
|
|||
});
|
||||
throw error;
|
||||
} finally {
|
||||
this._compactionAbortController = undefined;
|
||||
this._clearManualCompactionState();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2419,6 +2426,7 @@ export class AgentSession {
|
|||
return false;
|
||||
} finally {
|
||||
this._autoCompactionAbortController = undefined;
|
||||
this._resolveIdleWaitIfIdle();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3291,6 +3299,7 @@ export class AgentSession {
|
|||
return { editorText, cancelled: false, summaryEntry };
|
||||
} finally {
|
||||
this._branchSummaryAbortController = undefined;
|
||||
this._resolveIdleWaitIfIdle();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -156,9 +156,7 @@ async function createRuntimeHost(options: { withAuth: boolean; responseDelayMs:
|
|||
runtimeHost,
|
||||
cleanup: async () => {
|
||||
try {
|
||||
if (session.isStreaming) {
|
||||
await session.abort();
|
||||
}
|
||||
await session.abort();
|
||||
} catch {
|
||||
// ignore test cleanup failures
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,31 @@ function seedCompactableSession(harness: Harness): void {
|
|||
harness.session.agent.state.messages = harness.sessionManager.buildSessionContext().messages;
|
||||
}
|
||||
|
||||
async function createAbortableCompactionHarness(): Promise<{
|
||||
harness: Harness;
|
||||
compactionStarted: Promise<void>;
|
||||
}> {
|
||||
let markCompactionStarted = () => {};
|
||||
const compactionStarted = new Promise<void>((resolve) => {
|
||||
markCompactionStarted = resolve;
|
||||
});
|
||||
const harness = await createHarness({
|
||||
settings: { compaction: { keepRecentTokens: 1 } },
|
||||
extensionFactories: [
|
||||
(pi) => {
|
||||
pi.on("session_before_compact", async (event) => {
|
||||
return await new Promise<{ cancel: true }>((resolve) => {
|
||||
event.signal.addEventListener("abort", () => resolve({ cancel: true }), { once: true });
|
||||
markCompactionStarted();
|
||||
});
|
||||
});
|
||||
},
|
||||
],
|
||||
});
|
||||
seedCompactableSession(harness);
|
||||
return { harness, compactionStarted };
|
||||
}
|
||||
|
||||
describe("AgentSession compaction characterization", () => {
|
||||
const harnesses: Harness[] = [];
|
||||
|
||||
|
|
@ -637,28 +662,38 @@ describe("AgentSession compaction characterization", () => {
|
|||
});
|
||||
|
||||
it("cancels in-progress manual compaction when abortCompaction is called", async () => {
|
||||
const harness = await createHarness({
|
||||
settings: { compaction: { keepRecentTokens: 1 } },
|
||||
extensionFactories: [
|
||||
(pi) => {
|
||||
pi.on("session_before_compact", async (event) => {
|
||||
return await new Promise<{ cancel: true }>((resolve) => {
|
||||
event.signal.addEventListener("abort", () => resolve({ cancel: true }), { once: true });
|
||||
});
|
||||
});
|
||||
},
|
||||
],
|
||||
});
|
||||
const { harness, compactionStarted } = await createAbortableCompactionHarness();
|
||||
harnesses.push(harness);
|
||||
|
||||
await harness.session.prompt("one");
|
||||
await harness.session.prompt("two");
|
||||
|
||||
const compactPromise = harness.session.compact();
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
const compactExpectation = expect(compactPromise).rejects.toThrow("Compaction cancelled");
|
||||
await compactionStarted;
|
||||
harness.session.abortCompaction();
|
||||
|
||||
await expect(compactPromise).rejects.toThrow("Compaction cancelled");
|
||||
await compactExpectation;
|
||||
});
|
||||
|
||||
// Regression test for #8920.
|
||||
it("aborts an in-progress manual compaction and waits until the session is idle", async () => {
|
||||
const { harness, compactionStarted } = await createAbortableCompactionHarness();
|
||||
harnesses.push(harness);
|
||||
harness.setResponses([fauxAssistantMessage("continued")]);
|
||||
|
||||
const compactPromise = harness.session.compact();
|
||||
const compactExpectation = expect(compactPromise).rejects.toThrow("Compaction cancelled");
|
||||
await compactionStarted;
|
||||
await harness.session.abort();
|
||||
await compactExpectation;
|
||||
|
||||
expect(harness.eventsOfType("compaction_end").at(-1)).toMatchObject({
|
||||
reason: "manual",
|
||||
aborted: true,
|
||||
});
|
||||
expect(harness.session.isCompacting).toBe(false);
|
||||
expect(harness.session.isIdle).toBe(true);
|
||||
|
||||
await expect(harness.session.prompt("next prompt")).resolves.toBeUndefined();
|
||||
expect(harness.session.getLastAssistantText()).toBe("continued");
|
||||
});
|
||||
|
||||
it("resumes after threshold compaction when only agent-level queued messages exist", async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue