|
|
@@ -493,33 +493,45 @@ describe('MultiplexerSessionManager', () => {
|
|
|
expect(mockMultiplexer.closePane).not.toHaveBeenCalled();
|
|
|
});
|
|
|
|
|
|
- test('does not close never-seen pane when missing from status', async () => {
|
|
|
+ test('keeps missing running background job pane open', async () => {
|
|
|
const ctx = createMockContext();
|
|
|
+ const board = new BackgroundJobBoard();
|
|
|
+ board.registerLaunch({
|
|
|
+ taskID: 'running-background-job',
|
|
|
+ parentSessionID: 'parent-1',
|
|
|
+ agent: 'explorer',
|
|
|
+ });
|
|
|
mockMultiplexer.spawnPane.mockResolvedValue({
|
|
|
success: true,
|
|
|
- paneId: 'p-never-seen-orphan',
|
|
|
+ paneId: 'p-running-background-job',
|
|
|
});
|
|
|
const manager = new MultiplexerSessionManager(
|
|
|
ctx,
|
|
|
defaultMultiplexerConfig,
|
|
|
+ board,
|
|
|
);
|
|
|
|
|
|
await manager.onSessionCreated({
|
|
|
type: 'session.created',
|
|
|
- properties: { info: { id: 'never-seen-orphan', parentID: 'p1' } },
|
|
|
+ properties: {
|
|
|
+ info: { id: 'running-background-job', parentID: 'parent-1' },
|
|
|
+ },
|
|
|
});
|
|
|
|
|
|
+ const tracked = (manager as any).sessions.get('running-background-job');
|
|
|
+ tracked.missingSince = Date.now() - 60_000;
|
|
|
+
|
|
|
setMockSessionStatuses({});
|
|
|
await (manager as any).pollSessions();
|
|
|
|
|
|
expect(mockMultiplexer.closePane).not.toHaveBeenCalled();
|
|
|
});
|
|
|
|
|
|
- test('ignores empty session status response without closing panes', async () => {
|
|
|
+ test('closes never-seen pane when no running background job exists', async () => {
|
|
|
const ctx = createMockContext();
|
|
|
mockMultiplexer.spawnPane.mockResolvedValue({
|
|
|
success: true,
|
|
|
- paneId: 'p-empty-status',
|
|
|
+ paneId: 'p-never-seen-orphan',
|
|
|
});
|
|
|
const manager = new MultiplexerSessionManager(
|
|
|
ctx,
|
|
|
@@ -528,23 +540,25 @@ describe('MultiplexerSessionManager', () => {
|
|
|
|
|
|
await manager.onSessionCreated({
|
|
|
type: 'session.created',
|
|
|
- properties: { info: { id: 'empty-status', parentID: 'p1' } },
|
|
|
+ properties: { info: { id: 'never-seen-orphan', parentID: 'p1' } },
|
|
|
});
|
|
|
|
|
|
- mockFetch.mockImplementationOnce(
|
|
|
- async () => new Response('', { status: 200 }),
|
|
|
- );
|
|
|
+ const tracked = (manager as any).sessions.get('never-seen-orphan');
|
|
|
+ tracked.missingSince = Date.now() - 60_000;
|
|
|
|
|
|
+ setMockSessionStatuses({});
|
|
|
await (manager as any).pollSessions();
|
|
|
|
|
|
- expect(mockMultiplexer.closePane).not.toHaveBeenCalled();
|
|
|
+ expect(mockMultiplexer.closePane).toHaveBeenCalledWith(
|
|
|
+ 'p-never-seen-orphan',
|
|
|
+ );
|
|
|
});
|
|
|
|
|
|
- test('does not close previously seen session when later missing from status', async () => {
|
|
|
+ test('ignores empty session status response without closing panes', async () => {
|
|
|
const ctx = createMockContext();
|
|
|
mockMultiplexer.spawnPane.mockResolvedValue({
|
|
|
success: true,
|
|
|
- paneId: 'p-seen-before-missing',
|
|
|
+ paneId: 'p-empty-status',
|
|
|
});
|
|
|
const manager = new MultiplexerSessionManager(
|
|
|
ctx,
|
|
|
@@ -553,23 +567,26 @@ describe('MultiplexerSessionManager', () => {
|
|
|
|
|
|
await manager.onSessionCreated({
|
|
|
type: 'session.created',
|
|
|
- properties: { info: { id: 'seen-before-missing', parentID: 'p1' } },
|
|
|
+ properties: { info: { id: 'empty-status', parentID: 'p1' } },
|
|
|
});
|
|
|
|
|
|
- setMockSessionStatuses({ 'seen-before-missing': { type: 'busy' } });
|
|
|
- await (manager as any).pollSessions();
|
|
|
+ const tracked = (manager as any).sessions.get('empty-status');
|
|
|
+ tracked.seenInStatus = true;
|
|
|
+ tracked.missingSince = Date.now() - 60_000;
|
|
|
+ mockFetch.mockImplementationOnce(
|
|
|
+ async () => new Response('', { status: 200 }),
|
|
|
+ );
|
|
|
|
|
|
- setMockSessionStatuses({});
|
|
|
await (manager as any).pollSessions();
|
|
|
|
|
|
expect(mockMultiplexer.closePane).not.toHaveBeenCalled();
|
|
|
});
|
|
|
|
|
|
- test('does not respawn duplicate pane when missing session becomes busy again', async () => {
|
|
|
+ test('keeps missing cleanup for sessions previously seen in status', async () => {
|
|
|
const ctx = createMockContext();
|
|
|
mockMultiplexer.spawnPane.mockResolvedValue({
|
|
|
success: true,
|
|
|
- paneId: 'p-seen-before-busy-again',
|
|
|
+ paneId: 'p-seen-before-missing',
|
|
|
});
|
|
|
const manager = new MultiplexerSessionManager(
|
|
|
ctx,
|
|
|
@@ -578,27 +595,21 @@ describe('MultiplexerSessionManager', () => {
|
|
|
|
|
|
await manager.onSessionCreated({
|
|
|
type: 'session.created',
|
|
|
- properties: {
|
|
|
- info: { id: 'seen-before-busy-again', parentID: 'p1' },
|
|
|
- },
|
|
|
+ properties: { info: { id: 'seen-before-missing', parentID: 'p1' } },
|
|
|
});
|
|
|
|
|
|
- setMockSessionStatuses({ 'seen-before-busy-again': { type: 'busy' } });
|
|
|
+ setMockSessionStatuses({ 'seen-before-missing': { type: 'busy' } });
|
|
|
await (manager as any).pollSessions();
|
|
|
|
|
|
+ const tracked = (manager as any).sessions.get('seen-before-missing');
|
|
|
+ tracked.missingSince = Date.now() - 60_000;
|
|
|
+
|
|
|
setMockSessionStatuses({});
|
|
|
await (manager as any).pollSessions();
|
|
|
|
|
|
- await manager.onSessionStatus({
|
|
|
- type: 'session.status',
|
|
|
- properties: {
|
|
|
- sessionID: 'seen-before-busy-again',
|
|
|
- status: { type: 'busy' },
|
|
|
- },
|
|
|
- });
|
|
|
-
|
|
|
- expect(mockMultiplexer.closePane).not.toHaveBeenCalled();
|
|
|
- expect(mockMultiplexer.spawnPane).toHaveBeenCalledTimes(1);
|
|
|
+ expect(mockMultiplexer.closePane).toHaveBeenCalledWith(
|
|
|
+ 'p-seen-before-missing',
|
|
|
+ );
|
|
|
});
|
|
|
|
|
|
test('polls the actual serverUrl instead of the plugin SDK default URL', async () => {
|