|
|
@@ -16,6 +16,8 @@ import { log } from '../utils/logger';
|
|
|
interface BackgroundJobReader {
|
|
|
getState(sessionId: string): BackgroundJobState | undefined;
|
|
|
isRunning(sessionId: string): boolean;
|
|
|
+ deferIfRunning(sessionId: string): boolean;
|
|
|
+ clearDeferredClose(sessionId: string): void;
|
|
|
}
|
|
|
|
|
|
interface TrackedSession {
|
|
|
@@ -38,7 +40,6 @@ interface SharedSessionState {
|
|
|
knownSessions: Map<string, KnownSession>;
|
|
|
spawningSessions: Set<string>;
|
|
|
closingSessions: Map<string, Promise<void>>;
|
|
|
- deferredIdleCloses: Set<string>;
|
|
|
}
|
|
|
|
|
|
interface SessionEvent {
|
|
|
@@ -71,7 +72,6 @@ function getSharedState(): SharedSessionState {
|
|
|
knownSessions: new Map(),
|
|
|
spawningSessions: new Set(),
|
|
|
closingSessions: new Map(),
|
|
|
- deferredIdleCloses: new Set(),
|
|
|
};
|
|
|
|
|
|
return globalWithState[SHARED_STATE_KEY];
|
|
|
@@ -83,7 +83,6 @@ export function resetMultiplexerSessionManagerState(): void {
|
|
|
state.knownSessions.clear();
|
|
|
state.spawningSessions.clear();
|
|
|
state.closingSessions.clear();
|
|
|
- state.deferredIdleCloses.clear();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -101,7 +100,6 @@ export class MultiplexerSessionManager {
|
|
|
private knownSessions: SharedSessionState['knownSessions'];
|
|
|
private spawningSessions: SharedSessionState['spawningSessions'];
|
|
|
private closingSessions: SharedSessionState['closingSessions'];
|
|
|
- private deferredIdleCloses: SharedSessionState['deferredIdleCloses'];
|
|
|
private pollInterval?: ReturnType<typeof setInterval>;
|
|
|
private enabled = false;
|
|
|
|
|
|
@@ -115,7 +113,6 @@ export class MultiplexerSessionManager {
|
|
|
this.knownSessions = sharedState.knownSessions;
|
|
|
this.spawningSessions = sharedState.spawningSessions;
|
|
|
this.closingSessions = sharedState.closingSessions;
|
|
|
- this.deferredIdleCloses = sharedState.deferredIdleCloses;
|
|
|
|
|
|
this.directory = ctx.directory;
|
|
|
const defaultPort = process.env.OPENCODE_PORT ?? '4096';
|
|
|
@@ -290,7 +287,7 @@ export class MultiplexerSessionManager {
|
|
|
|
|
|
if (statusType) {
|
|
|
if (statusType !== 'busy') {
|
|
|
- this.deferredIdleCloses.delete(sessionId);
|
|
|
+ this.backgroundJobBoard?.clearDeferredClose(sessionId);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -322,7 +319,7 @@ export class MultiplexerSessionManager {
|
|
|
backgroundJobState: this.backgroundJobState(sessionId),
|
|
|
});
|
|
|
|
|
|
- this.deferredIdleCloses.delete(sessionId);
|
|
|
+ this.backgroundJobBoard?.clearDeferredClose(sessionId);
|
|
|
await this.closeSession(sessionId, 'deleted');
|
|
|
}
|
|
|
|
|
|
@@ -374,7 +371,7 @@ export class MultiplexerSessionManager {
|
|
|
if (!status) continue;
|
|
|
|
|
|
if (status.type !== 'idle') {
|
|
|
- this.deferredIdleCloses.delete(sessionId);
|
|
|
+ this.backgroundJobBoard?.clearDeferredClose(sessionId);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
@@ -419,7 +416,7 @@ export class MultiplexerSessionManager {
|
|
|
): Promise<void> {
|
|
|
if (reason === 'deleted') {
|
|
|
this.knownSessions.delete(sessionId);
|
|
|
- this.deferredIdleCloses.delete(sessionId);
|
|
|
+ this.backgroundJobBoard?.clearDeferredClose(sessionId);
|
|
|
}
|
|
|
|
|
|
const existingClose = this.closingSessions.get(sessionId);
|
|
|
@@ -457,8 +454,7 @@ export class MultiplexerSessionManager {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- if (reason === 'idle' && this.isRunningBackgroundJob(sessionId)) {
|
|
|
- this.deferredIdleCloses.add(sessionId);
|
|
|
+ if (reason === 'idle' && !this.shouldCloseNow(sessionId)) {
|
|
|
log(
|
|
|
'[multiplexer-session-manager] close skipped; background job running',
|
|
|
{
|
|
|
@@ -472,7 +468,6 @@ export class MultiplexerSessionManager {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- this.deferredIdleCloses.delete(sessionId);
|
|
|
this.sessions.delete(sessionId);
|
|
|
|
|
|
log('[multiplexer-session-manager] closing session pane', {
|
|
|
@@ -586,7 +581,7 @@ export class MultiplexerSessionManager {
|
|
|
directory: known.directory,
|
|
|
ownerInstanceId: this.instanceId,
|
|
|
});
|
|
|
- this.deferredIdleCloses.delete(sessionId);
|
|
|
+ this.backgroundJobBoard?.clearDeferredClose(sessionId);
|
|
|
|
|
|
log('[multiplexer-session-manager] pane respawned on busy', {
|
|
|
instanceId: this.instanceId,
|
|
|
@@ -622,14 +617,8 @@ export class MultiplexerSessionManager {
|
|
|
return this.backgroundJobBoard?.getState(sessionId);
|
|
|
}
|
|
|
|
|
|
- private isRunningBackgroundJob(sessionId: string): boolean {
|
|
|
- return this.backgroundJobBoard?.isRunning(sessionId) ?? false; // ponytail: intent-revealing query
|
|
|
- }
|
|
|
-
|
|
|
- async retryDeferredIdleClose(sessionId: string): Promise<void> {
|
|
|
- if (!this.enabled) return;
|
|
|
- if (!this.deferredIdleCloses.has(sessionId)) return;
|
|
|
- await this.closeSession(sessionId, 'idle');
|
|
|
+ private shouldCloseNow(sessionId: string): boolean {
|
|
|
+ return this.backgroundJobBoard?.deferIfRunning(sessionId) ?? true;
|
|
|
}
|
|
|
|
|
|
async cleanup(): Promise<void> {
|
|
|
@@ -659,7 +648,8 @@ export class MultiplexerSessionManager {
|
|
|
this.knownSessions.clear();
|
|
|
this.spawningSessions.clear();
|
|
|
this.closingSessions.clear();
|
|
|
- this.deferredIdleCloses.clear();
|
|
|
+ // ponytail: deferred state lives in coordinator, not here
|
|
|
+ // Note: coordinator has same lifetime as plugin, so no explicit cleanup needed
|
|
|
|
|
|
log('[multiplexer-session-manager] cleanup complete');
|
|
|
}
|