fix(plugins): fan Pi session-only status out to plugin subscribers

The providerSessionOnly early-return in applyNormalizedStatus emitted to
onAgentStatus (main-window fanout) but skipped enrichedStatusListeners, so
plugins subscribed to agent.status.changed silently missed every Pi
session_start event. Route both emit sites through one helper so a future
early return cannot drop the plugin tap again.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil
2026-07-27 00:27:26 -07:00
co-authored by Orca
parent 594c2c5deb
commit 88c3ea7528
2 changed files with 40 additions and 2 deletions
+32
View File
@@ -7083,6 +7083,38 @@ describe('AgentHookServer ingestRemote', () => {
}
})
it('fans a Pi session-only status out to plugins, not just the renderer', () => {
const server = new AgentHookServer()
const rendererListener = vi.fn()
const pluginListener = vi.fn()
server.setListener(rendererListener)
server.subscribeEnrichedStatus(pluginListener)
server.ingestRemote(
{
paneKey: PANE,
tabId: 'tab-1',
worktreeId: 'wt-1',
providerSession: {
key: 'session_id',
id: 'pi-session-1',
transcriptPath: '/tmp/pi-session-1.jsonl'
},
providerSessionOnly: true,
payload: { state: 'done', prompt: '', agentType: 'pi' }
},
'conn-1'
)
// The session-only path returns early, so it must not skip the plugin tap.
expect(rendererListener).toHaveBeenCalledWith(
expect.objectContaining({ paneKey: PANE, providerSessionOnly: true })
)
expect(pluginListener).toHaveBeenCalledWith(
expect.objectContaining({ paneKey: PANE, providerSessionOnly: true })
)
})
it('rejects invalid remote metadata-only session envelopes', () => {
const server = new AgentHookServer()
const listener = vi.fn()
+8 -2
View File
@@ -903,7 +903,7 @@ export class AgentHookServer {
this.state.lastStatusByPaneKey.set(enriched.paneKey, enriched)
this.scheduleStatusPersist()
this.notifyStatusChangeListeners()
this.onAgentStatus?.(enriched)
this.emitEnrichedStatus(enriched)
return enriched
}
const stateReconciledPayload =
@@ -1015,6 +1015,13 @@ export class AgentHookServer {
this.state.lastStatusByPaneKey.set(enriched.paneKey, enriched)
this.scheduleStatusPersist()
this.notifyStatusChangeListeners()
this.emitEnrichedStatus(enriched)
return enriched
}
// Why: every status emit must reach plugins too, so a new early-return path
// upstream cannot silently leave the plugin tap behind the main-window fanout.
private emitEnrichedStatus(enriched: EnrichedAgentHookEventPayload): void {
this.onAgentStatus?.(enriched)
for (const listener of this.enrichedStatusListeners) {
try {
@@ -1023,7 +1030,6 @@ export class AgentHookServer {
console.error('[agent-hooks] enriched status listener threw', err)
}
}
return enriched
}
private clearAssistantMessageRetry(paneKey: string): void {