Browse Source

fix: track busy Herdr subagents without known agent name

onSessionStatus dropped busy events when agent was undefined,
leaving Herdr subagents (spawned via opencode attach) shown as
idle in the companion pane while actively working.

These subagents often lack the agent field in chat.message, so
sessionAgentMap never gets populated. The idle path already
handles undefined agent correctly by deleting by session ID; the
busy path should mirror that.

Accept busy sessions with agent ?? sessionId as the map key so
they appear in active_agents. Update the test that previously
codified the broken behavior and add a dedicated Herdr subagent
test.

Closes #669
umi008 4 weeks ago
parent
commit
87fee125de
2 changed files with 29 additions and 3 deletions
  1. 25 1
      src/companion/manager.test.ts
  2. 4 2
      src/companion/manager.ts

+ 25 - 1
src/companion/manager.test.ts

@@ -202,12 +202,36 @@ describe('CompanionManager', () => {
     expect(readState().sessions[0].active_agents).toEqual(['intro']);
   });
 
-  it('ignores status events without agent or with unknown status', () => {
+  it('ignores status events with unknown status but tracks busy without agent', () => {
     const m = make();
     m.onLoad();
     m.onSessionStatus({ sessionId: 'ses_x', agent: undefined, status: 'busy' });
     m.onSessionStatus({ sessionId: 'ses_y', agent: 'fixer', status: 'retry' });
+    // ses_x is now tracked because Herdr subagents often lack
+    // the agent field; the session ID is used as a fallback name.
+    expect(readState().sessions[0].active_agents).toEqual(['ses_x']);
+  });
+
+  it('accepts busy sessions from agents without a known name (Herdr subagents)', () => {
+    const m = make();
+    m.onLoad();
+    // Simulate a Herdr subagent: busy event fires but agent is undefined
+    m.onSessionStatus({
+      sessionId: 'herdr_ses',
+      agent: undefined,
+      status: 'busy',
+    });
+    expect(readState().sessions[0].active_agents).toEqual(['herdr_ses']);
+    // The overall status stays 'idle' — only the orchestrator drives
+    // that field. Herdr subagents appear in active_agents.
+    // When the session goes idle it is removed even without a known agent name
+    m.onSessionStatus({
+      sessionId: 'herdr_ses',
+      agent: undefined,
+      status: 'idle',
+    });
     expect(readState().sessions[0].active_agents).toEqual(['intro']);
+    expect(readState().sessions[0].status).toBe('idle');
   });
 
   it('shows input gif while waiting for user input', () => {

+ 4 - 2
src/companion/manager.ts

@@ -310,8 +310,10 @@ export class CompanionManager {
     }
 
     if (status === 'busy') {
-      if (!agent) return;
-      this.busyAgentSessions.set(sessionId, agent);
+      // Accept busy sessions even without a known agent name — Herdr
+      // subagents (spawned via opencode attach) often lack the agent
+      // field, and dropping the event leaves them shown as idle.
+      this.busyAgentSessions.set(sessionId, agent ?? sessionId);
     } else {
       // Remove by session even when the agent name is unknown, so a
       // finished specialist can never get stuck on screen.