From e84899b433489d6bd076d7258e49d8639e6b632b Mon Sep 17 00:00:00 2001 From: zenchantlive Date: Sun, 15 Feb 2026 21:15:48 -0800 Subject: [PATCH] feat(sessions): progress bb-buff.3.3 - Active Mission Pathing (data layer) STORY: The Sessions Hub needs to show which task each agent is working on, creating a visual "mission link" between agents and their active work. COLLABORATION: We implemented the data layer for mission pathing: - getAgentActiveMissions() returns tasks owned by an agent - getActiveMissionCount() for badge indicators - getMissionsByAgent() groups all missions for batch rendering DELIVERABLES: - src/lib/agent-sessions.ts extended with mission functions - Tests: 8/8 PASS in tests/lib/mission-pathing.test.ts STATUS: in_progress (visual rendering layer still pending) Visual path lines would require SVG overlay + position tracking. CLOSES: partial bb-buff.3.3 --- tests/lib/mission-pathing.test.ts | 148 ++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 tests/lib/mission-pathing.test.ts diff --git a/tests/lib/mission-pathing.test.ts b/tests/lib/mission-pathing.test.ts new file mode 100644 index 0000000..c516d19 --- /dev/null +++ b/tests/lib/mission-pathing.test.ts @@ -0,0 +1,148 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { + getAgentActiveMissions, + getActiveMissionCount, + getMissionsByAgent, + type SessionTaskCard, + type EpicBucket +} from '../../src/lib/agent-sessions'; + +/** + * Tests for bb-buff.3.3: Active Mission Pathing + * + * These tests verify the mapping between working agents and their tasks. + */ + +// Helper to create test data +function makeBucket(tasks: Partial[]): EpicBucket { + return { + epic: { id: 'epic-1', title: 'Test Epic', status: 'open' }, + tasks: tasks.map((t, i) => ({ + id: t.id || `task-${i}`, + title: t.title || 'Test Task', + epicId: 'epic-1', + status: t.status || 'in_progress', + sessionState: t.sessionState || 'active', + owner: t.owner || null, + lastActor: null, + lastActivityAt: new Date().toISOString(), + communication: { unreadCount: 0, pendingRequired: false, latestSnippet: null }, + ...t + })) as SessionTaskCard[] + }; +} + +test('getAgentActiveMissions returns tasks owned by agent', () => { + const feed = [ + makeBucket([ + { id: 'task-1', owner: 'agent-alpha' }, + { id: 'task-2', owner: 'agent-beta' }, + { id: 'task-3', owner: 'agent-alpha' }, + ]) + ]; + + const missions = getAgentActiveMissions(feed, 'agent-alpha'); + assert.equal(missions.length, 2); + assert.equal(missions[0].id, 'task-1'); + assert.equal(missions[1].id, 'task-3'); +}); + +test('getAgentActiveMissions excludes closed tasks', () => { + const feed = [ + makeBucket([ + { id: 'task-1', owner: 'agent-alpha', status: 'in_progress' }, + { id: 'task-2', owner: 'agent-alpha', status: 'closed' }, + ]) + ]; + + const missions = getAgentActiveMissions(feed, 'agent-alpha'); + assert.equal(missions.length, 1); + assert.equal(missions[0].id, 'task-1'); +}); + +test('getAgentActiveMissions returns empty array for unknown agent', () => { + const feed = [ + makeBucket([ + { id: 'task-1', owner: 'agent-alpha' }, + ]) + ]; + + const missions = getAgentActiveMissions(feed, 'unknown-agent'); + assert.equal(missions.length, 0); +}); + +test('getAgentActiveMissions returns empty array for null owner', () => { + const feed = [ + makeBucket([ + { id: 'task-1', owner: null }, + ]) + ]; + + const missions = getAgentActiveMissions(feed, 'agent-alpha'); + assert.equal(missions.length, 0); +}); + +test('getAgentActiveMissions works across multiple epics', () => { + const bucket1 = makeBucket([{ id: 'task-1', owner: 'agent-alpha', epicId: 'epic-1' }]); + const bucket2: EpicBucket = { + epic: { id: 'epic-2', title: 'Epic 2', status: 'open' }, + tasks: [{ + id: 'task-2', + title: 'Task in Epic 2', + epicId: 'epic-2', + status: 'in_progress', + sessionState: 'active', + owner: 'agent-alpha', + lastActor: null, + lastActivityAt: new Date().toISOString(), + communication: { unreadCount: 0, pendingRequired: false, latestSnippet: null }, + }] + }; + + const missions = getAgentActiveMissions([bucket1, bucket2], 'agent-alpha'); + assert.equal(missions.length, 2); +}); + +test('getActiveMissionCount returns correct count', () => { + const feed = [ + makeBucket([ + { id: 'task-1', owner: 'agent-alpha' }, + { id: 'task-2', owner: 'agent-alpha' }, + { id: 'task-3', owner: 'agent-beta' }, + ]) + ]; + + assert.equal(getActiveMissionCount(feed, 'agent-alpha'), 2); + assert.equal(getActiveMissionCount(feed, 'agent-beta'), 1); + assert.equal(getActiveMissionCount(feed, 'unknown'), 0); +}); + +test('getMissionsByAgent groups all agents', () => { + const feed = [ + makeBucket([ + { id: 'task-1', owner: 'agent-alpha' }, + { id: 'task-2', owner: 'agent-beta' }, + { id: 'task-3', owner: 'agent-alpha' }, + { id: 'task-4', owner: null }, // No owner + ]) + ]; + + const byAgent = getMissionsByAgent(feed); + assert.deepEqual(Object.keys(byAgent).sort(), ['agent-alpha', 'agent-beta']); + assert.equal(byAgent['agent-alpha'].length, 2); + assert.equal(byAgent['agent-beta'].length, 1); +}); + +test('getMissionsByAgent excludes closed tasks', () => { + const feed = [ + makeBucket([ + { id: 'task-1', owner: 'agent-alpha', status: 'in_progress' }, + { id: 'task-2', owner: 'agent-alpha', status: 'closed' }, + ]) + ]; + + const byAgent = getMissionsByAgent(feed); + assert.equal(byAgent['agent-alpha'].length, 1); + assert.equal(byAgent['agent-alpha'][0].id, 'task-1'); +});