fix: isolate auto-compaction abort state

Fixes #275
This commit is contained in:
can1357
2026-03-09 15:54:24 +01:00
parent d74cd0de5c
commit 2ec4401bcd
2 changed files with 27 additions and 24 deletions
@@ -3726,11 +3726,11 @@ export class AgentSession {
let action: "context-full" | "handoff" =
compactionSettings.strategy === "handoff" && reason !== "overflow" ? "handoff" : "context-full";
await this.#emitSessionEvent({ type: "auto_compaction_start", reason, action });
// Properly abort and null existing controller before replacing
if (this.#autoCompactionAbortController) {
this.#autoCompactionAbortController.abort();
}
this.#autoCompactionAbortController = new AbortController();
// Abort any older auto-compaction before installing this run's controller.
this.#autoCompactionAbortController?.abort();
const autoCompactionAbortController = new AbortController();
this.#autoCompactionAbortController = autoCompactionAbortController;
const autoCompactionSignal = autoCompactionAbortController.signal;
try {
if (compactionSettings.strategy === "handoff" && reason !== "overflow") {
@@ -3740,7 +3740,7 @@ export class AgentSession {
signal: this.#autoCompactionAbortController.signal,
});
if (!handoffResult) {
const aborted = this.#autoCompactionAbortController.signal.aborted;
const aborted = autoCompactionSignal.aborted;
if (aborted) {
await this.#emitSessionEvent({
type: "auto_compaction_end",
@@ -3824,7 +3824,7 @@ export class AgentSession {
preparation,
branchEntries: pathEntries,
customInstructions: undefined,
signal: this.#autoCompactionAbortController.signal,
signal: autoCompactionSignal,
})) as SessionBeforeCompactResult | undefined;
if (hookResult?.cancel) {
@@ -3889,7 +3889,7 @@ export class AgentSession {
candidate,
apiKey,
undefined,
this.#autoCompactionAbortController.signal,
autoCompactionSignal,
{
promptOverride: hookPrompt,
extraContext: hookContext,
@@ -3898,7 +3898,7 @@ export class AgentSession {
);
break;
} catch (error) {
if (this.#autoCompactionAbortController.signal.aborted) {
if (autoCompactionSignal.aborted) {
throw error;
}
@@ -3942,7 +3942,7 @@ export class AgentSession {
error: message,
model: `${candidate.provider}/${candidate.id}`,
});
await abortableSleep(delayMs, this.#autoCompactionAbortController.signal);
await abortableSleep(delayMs, autoCompactionSignal);
}
}
@@ -3966,7 +3966,7 @@ export class AgentSession {
preserveData = { ...(preserveData ?? {}), ...(compactResult.preserveData ?? {}) };
}
if (this.#autoCompactionAbortController.signal.aborted) {
if (autoCompactionSignal.aborted) {
await this.#emitSessionEvent({
type: "auto_compaction_end",
action,
@@ -4056,7 +4056,7 @@ export class AgentSession {
});
}
} catch (error) {
if (this.#autoCompactionAbortController?.signal.aborted) {
if (autoCompactionSignal.aborted) {
await this.#emitSessionEvent({
type: "auto_compaction_end",
action,
@@ -4079,7 +4079,9 @@ export class AgentSession {
: `Auto-compaction failed: ${errorMessage}`,
});
} finally {
this.#autoCompactionAbortController = undefined;
if (this.#autoCompactionAbortController === autoCompactionAbortController) {
this.#autoCompactionAbortController = undefined;
}
}
}
@@ -25,8 +25,8 @@ function getRuntimeSignals(): string[] {
}
/**
* Regression test: auto-compaction completion should resume the agent loop when
* there are queued agent-level messages (follow-up/steering/custom).
* Regression test: auto-compaction completion should resume the agent loop when
* there are queued agent-level messages (follow-up/steering/custom).
*/
describe("AgentSession auto-compaction queue resume", () => {
let tempDir: TempDir;
@@ -60,17 +60,17 @@ describe("AgentSession auto-compaction queue resume", () => {
"\t\t};",
"\t});",
'\tpi.on("auto_compaction_start", async (event) => {',
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
'\t\tsignals.push("compaction:start:" + event.reason);',
"\t});",
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
'\t\tsignals.push("compaction:start:" + event.reason);',
"\t});",
'\tpi.on("auto_compaction_end", async (event) => {',
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
'\t\tsignals.push("compaction:end:" + (event.aborted ? "aborted" : "ok"));',
"\t});",
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
'\t\tsignals.push("compaction:end:" + (event.aborted ? "aborted" : "ok"));',
"\t});",
'\tpi.on("todo_reminder", async (event) => {',
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
'\t\tsignals.push("todo:" + event.attempt + "/" + event.maxAttempts);',
"\t});",
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
'\t\tsignals.push("todo:" + event.attempt + "/" + event.maxAttempts);',
"\t});",
"}",
].join("\n"),
);
@@ -239,4 +239,5 @@ describe("AgentSession auto-compaction queue resume", () => {
expect(getRuntimeSignals()).toContain("todo:1/3");
expect(continueSpy).toHaveBeenCalledTimes(1);
});
});