fix(coding-agent): limit mnemopi shutdown timeout to interactive exit
This commit is contained in:
@@ -90,7 +90,12 @@ import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compa
|
||||
type: "text",
|
||||
};
|
||||
import type { AgentRegistry } from "../registry/agent-registry";
|
||||
import type { AgentSession, AgentSessionEvent, ResolvedRoleModel } from "../session/agent-session";
|
||||
import {
|
||||
SHUTDOWN_CONSOLIDATE_BUDGET_MS,
|
||||
type AgentSession,
|
||||
type AgentSessionEvent,
|
||||
type ResolvedRoleModel,
|
||||
} from "../session/agent-session";
|
||||
import type { CompactMode } from "../session/compact-modes";
|
||||
import { HistoryStorage } from "../session/history-storage";
|
||||
import type { SessionContext } from "../session/session-context";
|
||||
@@ -789,7 +794,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
getDraftText: () => this.editor.getText(),
|
||||
beginDispose: () => this.session.beginDispose(),
|
||||
saveDraft: text => this.sessionManager.saveDraft(text),
|
||||
disposeSession: () => this.session.dispose(),
|
||||
disposeSession: () => this.session.dispose({ mnemopiConsolidateTimeoutMs: SHUTDOWN_CONSOLIDATE_BUDGET_MS }),
|
||||
});
|
||||
this.#cleanupUnsubscribe = postmortem.register("session-teardown", () => this.#signalTeardown!());
|
||||
|
||||
@@ -3319,7 +3324,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
if (this.#signalTeardown) {
|
||||
await this.#signalTeardown();
|
||||
} else {
|
||||
await this.session.dispose();
|
||||
await this.session.dispose({ mnemopiConsolidateTimeoutMs: SHUTDOWN_CONSOLIDATE_BUDGET_MS });
|
||||
}
|
||||
|
||||
// Do not force a final render during teardown: disposed session/UI state can
|
||||
|
||||
@@ -446,17 +446,18 @@ const UNEXPECTED_STOP_TIMEOUT_MS = 4000;
|
||||
const EMPTY_STOP_MAX_RETRIES = 3;
|
||||
const RETRY_BACKOFF_MAX_DELAY_MS = 8_000;
|
||||
/**
|
||||
* Cap how long {@link AgentSession.dispose} waits for
|
||||
* `MnemopiSessionState.dispose()` to finish its consolidate pass on the
|
||||
* user-visible `/quit` / `/exit` shutdown path. Consolidate fires fresh
|
||||
* LLM fact extractions, each a 1–3 s round-trip, so an unbounded await
|
||||
* stalled `/quit` for many seconds even on minimal-activity sessions
|
||||
* (issue #3641). Anything still in flight when the budget elapses is
|
||||
* detached to the background; the SQLite handles close once it settles.
|
||||
* Per-turn `maybeRetainOnAgentEnd` already retained earlier turns, so
|
||||
* the worst case is losing episodic promotion for the LAST few turns.
|
||||
* Budget for callers on the user-visible `/quit` / `/exit` shutdown path that
|
||||
* want to cap how long they wait for `MnemopiSessionState.dispose()` to finish
|
||||
* its consolidate pass. Consolidate fires fresh LLM fact extractions, each a
|
||||
* 1–3 s round-trip, so interactive shutdown passes this budget to keep the
|
||||
* UI responsive. Callers that keep the process/session host alive must omit it
|
||||
* so dispose still awaits the full consolidate-then-close pipeline.
|
||||
*/
|
||||
const SHUTDOWN_CONSOLIDATE_BUDGET_MS = 1_500;
|
||||
export const SHUTDOWN_CONSOLIDATE_BUDGET_MS = 1_500;
|
||||
|
||||
export interface AgentSessionDisposeOptions {
|
||||
mnemopiConsolidateTimeoutMs?: number;
|
||||
}
|
||||
|
||||
type CompactionCheckResult = Readonly<{
|
||||
deferredHandoff: boolean;
|
||||
@@ -5327,12 +5328,12 @@ export class AgentSession {
|
||||
* double-drain the owned `AsyncJobManager` (issue #4080).
|
||||
*/
|
||||
#disposeCall?: Promise<void>;
|
||||
dispose(): Promise<void> {
|
||||
if (!this.#disposeCall) this.#disposeCall = this.#doDispose();
|
||||
dispose(options: AgentSessionDisposeOptions = {}): Promise<void> {
|
||||
if (!this.#disposeCall) this.#disposeCall = this.#doDispose(options);
|
||||
return this.#disposeCall;
|
||||
}
|
||||
|
||||
async #doDispose(): Promise<void> {
|
||||
async #doDispose(options: AgentSessionDisposeOptions = {}): Promise<void> {
|
||||
this.beginDispose();
|
||||
this.#recordSessionExit("dispose");
|
||||
this.#cancelExitRecorder?.();
|
||||
@@ -5449,7 +5450,7 @@ export class AgentSession {
|
||||
this.setHindsightSessionState(undefined);
|
||||
hindsightState?.dispose();
|
||||
const mnemopiState = setMnemopiSessionState(this, undefined);
|
||||
await mnemopiState?.dispose({ timeoutMs: SHUTDOWN_CONSOLIDATE_BUDGET_MS });
|
||||
await mnemopiState?.dispose({ timeoutMs: options.mnemopiConsolidateTimeoutMs });
|
||||
// Tear down the embeddings subprocess AFTER mnemopi state.dispose:
|
||||
// consolidate-on-dispose may still call `embed()` to store the final
|
||||
// memories, and that round-trips through the worker we are about to
|
||||
|
||||
Reference in New Issue
Block a user