Procházet zdrojové kódy

fix: resolve greptile follow-up issues

Alvin Unreal před 3 měsíci
rodič
revize
d2338c02cc

+ 89 - 0
src/hooks/task-session-manager/index.test.ts

@@ -423,4 +423,93 @@ describe('task-session-manager hook', () => {
 
     expect(system.system).toEqual(['base']);
   });
+
+  test('deduplicates pending call order when a resume call is recorded twice', async () => {
+    const { hook } = createHook();
+
+    await hook['tool.execute.before'](
+      {
+        tool: 'task',
+        sessionID: 'parent-1',
+        callID: 'call-1',
+      },
+      {
+        args: {
+          subagent_type: 'explorer',
+          description: 'config schema',
+        },
+      },
+    );
+    await hook['tool.execute.after'](
+      {
+        tool: 'task',
+        sessionID: 'parent-1',
+        callID: 'call-1',
+      },
+      {
+        output:
+          'task_id: child-1 (for resuming to continue this task if needed)',
+      },
+    );
+
+    await hook['tool.execute.before'](
+      {
+        tool: 'task',
+        sessionID: 'parent-1',
+        callID: 'call-2',
+      },
+      {
+        args: {
+          subagent_type: 'explorer',
+          description: 'continue schema work',
+          task_id: 'exp-1',
+        },
+      },
+    );
+    await hook['tool.execute.after'](
+      {
+        tool: 'task',
+        sessionID: 'parent-1',
+        callID: 'call-2',
+      },
+      {
+        output: '[ERROR] Session not found',
+      },
+    );
+
+    await hook['tool.execute.before'](
+      {
+        tool: 'task',
+        sessionID: 'parent-1',
+        callID: 'call-3',
+      },
+      {
+        args: {
+          subagent_type: 'oracle',
+          description: 'architecture review',
+        },
+      },
+    );
+    await hook['tool.execute.after'](
+      {
+        tool: 'task',
+        sessionID: 'parent-1',
+        callID: 'call-3',
+      },
+      {
+        output:
+          'task_id: child-3 (for resuming to continue this task if needed)',
+      },
+    );
+
+    const system = { system: ['base'] };
+    await hook['experimental.chat.system.transform'](
+      { sessionID: 'parent-1' },
+      system,
+    );
+
+    expect(system.system.join('\n')).toContain(
+      'oracle: ora-1 architecture review',
+    );
+  });
 });

+ 5 - 0
src/hooks/task-session-manager/index.ts

@@ -64,6 +64,11 @@ export function createTaskSessionManagerHook(
   }
 
   function rememberPendingCall(call: PendingTaskCall): void {
+    const existingIndex = pendingCallOrder.indexOf(call.callId);
+    if (existingIndex >= 0) {
+      pendingCallOrder.splice(existingIndex, 1);
+    }
+
     pendingCalls.set(call.callId, call);
     pendingCallOrder.push(call.callId);
 

+ 59 - 3
src/multiplexer/session-manager.test.ts

@@ -48,9 +48,14 @@ const defaultMultiplexerConfig = {
 
 describe('MultiplexerSessionManager', () => {
   beforeEach(() => {
-    mockMultiplexer.spawnPane.mockClear();
-    mockMultiplexer.closePane.mockClear();
-    mockMultiplexer.isInsideSession.mockClear();
+    mockMultiplexer.spawnPane.mockReset();
+    mockMultiplexer.spawnPane.mockResolvedValue({
+      success: true,
+      paneId: '%mock-pane',
+    });
+    mockMultiplexer.closePane.mockReset();
+    mockMultiplexer.closePane.mockResolvedValue(true);
+    mockMultiplexer.isInsideSession.mockReset();
     mockMultiplexer.isInsideSession.mockReturnValue(true);
   });
 
@@ -275,6 +280,57 @@ describe('MultiplexerSessionManager', () => {
 
       expect(mockMultiplexer.spawnPane).not.toHaveBeenCalled();
     });
+
+    test('re-checks tracked sessions after async respawn guard', async () => {
+      const ctx = createMockContext();
+      const manager = new MultiplexerSessionManager(
+        ctx,
+        defaultMultiplexerConfig,
+      );
+
+      mockMultiplexer.spawnPane
+        .mockResolvedValueOnce({ success: true, paneId: 'p-1' })
+        .mockResolvedValueOnce({
+          success: true,
+          paneId: 'p-should-not-happen',
+        });
+
+      await manager.onSessionCreated({
+        type: 'session.created',
+        properties: {
+          info: {
+            id: 'child-999',
+            parentID: 'parent-999',
+            title: 'Worker',
+            directory: '/task/dir',
+          },
+        },
+      });
+
+      ctx.client.session.status.mockResolvedValue({
+        data: { 'child-999': { type: 'idle' } },
+      });
+      await (manager as any).pollSessions();
+
+      const respawnPromise = (manager as any).respawnIfKnown('child-999');
+
+      (manager as any).sessions.set('child-999', {
+        sessionId: 'child-999',
+        paneId: 'p-existing',
+        parentId: 'parent-999',
+        title: 'Worker',
+        directory: '/task/dir',
+        createdAt: Date.now(),
+        lastSeenAt: Date.now(),
+      });
+
+      await respawnPromise;
+
+      expect(mockMultiplexer.spawnPane).toHaveBeenCalledTimes(1);
+      expect((manager as any).sessions.get('child-999')?.paneId).toBe(
+        'p-existing',
+      );
+    });
   });
 
   describe('cleanup', () => {

+ 2 - 0
src/multiplexer/session-manager.ts

@@ -282,6 +282,8 @@ export class MultiplexerSessionManager {
       return;
     }
 
+    if (this.sessions.has(sessionId)) return;
+
     log(
       '[multiplexer-session-manager] child session busy again, respawning pane',
       {