From a123b2e24736e8dd8988dbc56308f3bc3826d74d Mon Sep 17 00:00:00 2001 From: 777genius Date: Thu, 23 Apr 2026 01:13:17 +0300 Subject: [PATCH] fix(team): surface permission-blocked launch state in graph --- .../renderer/adapters/TeamGraphAdapter.ts | 13 +++++---- .../agent-graph/TeamGraphAdapter.test.ts | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/features/agent-graph/renderer/adapters/TeamGraphAdapter.ts b/src/features/agent-graph/renderer/adapters/TeamGraphAdapter.ts index b1c8a051..91d9969a 100644 --- a/src/features/agent-graph/renderer/adapters/TeamGraphAdapter.ts +++ b/src/features/agent-graph/renderer/adapters/TeamGraphAdapter.ts @@ -518,7 +518,7 @@ export class TeamGraphAdapter { label: member.name, state: hasRunningTool ? 'tool_calling' - : TeamGraphAdapter.#mapMemberStatus(member.status, spawn?.status), + : TeamGraphAdapter.#mapMemberStatus(member.status, spawn), color: member.color ?? undefined, role: member.role ?? undefined, runtimeLabel: TeamGraphAdapter.#getRuntimeLabel( @@ -1128,7 +1128,7 @@ export class TeamGraphAdapter { if (spawn?.launchState === 'failed_to_start' || spawn?.status === 'error') { return { exceptionTone: 'error', exceptionLabel: 'spawn failed' }; } - if (pendingApproval) { + if (pendingApproval || spawn?.launchState === 'runtime_pending_permission') { return { exceptionTone: 'warning', exceptionLabel: 'awaiting approval' }; } if (spawn?.status === 'waiting' || spawn?.status === 'spawning') { @@ -1144,10 +1144,11 @@ export class TeamGraphAdapter { return undefined; } - static #mapMemberStatus(status: string, spawnStatus?: string): GraphNodeState { - if (spawnStatus === 'spawning') return 'thinking'; - if (spawnStatus === 'error') return 'error'; - if (spawnStatus === 'waiting') return 'waiting'; + static #mapMemberStatus(status: string, spawn?: MemberSpawnStatusEntry): GraphNodeState { + if (spawn?.launchState === 'runtime_pending_permission') return 'waiting'; + if (spawn?.status === 'spawning') return 'thinking'; + if (spawn?.status === 'error') return 'error'; + if (spawn?.status === 'waiting') return 'waiting'; switch (status) { case 'active': return 'active'; diff --git a/test/renderer/features/agent-graph/TeamGraphAdapter.test.ts b/test/renderer/features/agent-graph/TeamGraphAdapter.test.ts index 62240baf..48b7d81b 100644 --- a/test/renderer/features/agent-graph/TeamGraphAdapter.test.ts +++ b/test/renderer/features/agent-graph/TeamGraphAdapter.test.ts @@ -1360,6 +1360,35 @@ describe('TeamGraphAdapter particles', () => { }); }); + it('treats permission-blocked spawn state as awaiting approval even without pending approval feed', () => { + const adapter = TeamGraphAdapter.create(); + const teamData = createBaseTeamData(); + + adapter.adapt(teamData, 'my-team'); + + const graph = adapter.adapt(teamData, 'my-team', { + alice: { + status: 'online', + launchState: 'runtime_pending_permission', + runtimeAlive: true, + agentToolAccepted: true, + bootstrapConfirmed: false, + hardFailure: false, + updatedAt: '2026-04-08T20:00:00.000Z', + }, + }); + + expect(findNode(graph, 'member:my-team:alice')).toMatchObject({ + state: 'waiting', + spawnStatus: 'online', + launchVisualState: 'permission_pending', + launchStatusLabel: 'awaiting permission', + exceptionTone: 'warning', + exceptionLabel: 'awaiting approval', + pendingApproval: false, + }); + }); + it('refreshes unread comment badges when comment read state changes without task changes', () => { const adapter = TeamGraphAdapter.create(); const teamData = createBaseTeamData({