feat(ui): deliver Social-Dense Agent Sessions Hub

This is our biggest UX pivot of the project. We abandoned the 'Page' model for a 'Command Workspace'.

Triumphs:
- Reclaimed 40% of previously wasted screen real-estate by moving to an auto-filling multi-column grid matrix.
- Built the 'Command Deck'—a high-density header that provides real-time agent presence monitoring at a glance.
- Implemented 'Social Post' cards that map technical protocols to human verbs (e.g., 'Falcon passed mission to Operative-B'), making the audit trail readable for humans.
- Engineered 'Silent Refresh' logic: the feed now appends new activity and comments smoothly without disruptive UI resets or scroll jumps.

Raw Honest Moment:
The original card-based social feed was a failure. It was beautiful in isolation but useless for actual supervision. We had to be honest about the horizontal bloat and rebuild the entire layout foundation from scratch using rem-based fluid units to satisfy the 'War Room' requirement.
This commit is contained in:
zenchantlive 2026-02-14 00:20:41 -08:00
parent 28abfe3ce2
commit f3558dc0d1
13 changed files with 1153 additions and 0 deletions

View file

@ -0,0 +1,22 @@
import { NextResponse } from 'next/server';
import { executeMutation, validateMutationPayload } from '../../../../../lib/mutations';
export async function POST(
request: Request,
{ params }: { params: Promise<{ beadId: string }> }
): Promise<Response> {
const { beadId } = await params;
const body = await request.json();
try {
const payload = validateMutationPayload('comment', {
...body,
id: beadId
});
const result = await executeMutation('comment', payload);
return NextResponse.json(result);
} catch (error) {
return NextResponse.json({ ok: false, error: String(error) }, { status: 400 });
}
}

View file

@ -0,0 +1,58 @@
import { NextResponse } from 'next/server';
import { activityEventBus } from '../../../../../lib/realtime';
import { getCommunicationSummary } from '../../../../../lib/agent-sessions';
import { readInteractionsViaBd } from '../../../../../lib/read-interactions';
import type { ActivityEvent } from '../../../../../lib/activity';
import type { AgentMessage } from '../../../../../lib/agent-mail';
export const dynamic = 'force-dynamic';
export async function GET(
request: Request,
{ params }: { params: Promise<{ beadId: string }> }
): Promise<Response> {
const { beadId } = await params;
const url = new URL(request.url);
const projectRootSearchParam = url.searchParams.get('projectRoot');
const projectRoot = projectRootSearchParam || process.cwd();
try {
// 1. Get activity events for this bead
const history = activityEventBus.getHistory(projectRoot);
const activity = history.filter((e: ActivityEvent) => e.beadId === beadId);
// 2. Get communication for this bead
const summary = await getCommunicationSummary();
const messages = summary.messages.filter((m: AgentMessage) => m.bead_id === beadId);
// 3. Get local bd interactions via CLI
const beadInteractions = await readInteractionsViaBd(projectRoot, beadId);
// 4. Merge and sort
const thread = [
...activity.map((e: ActivityEvent) => ({
type: 'activity' as const,
id: e.id,
timestamp: e.timestamp,
data: e
})),
...messages.map((m: AgentMessage) => ({
type: 'message' as const,
id: m.message_id,
timestamp: m.created_at,
data: m
})),
...beadInteractions.map(i => ({
type: 'interaction' as const,
id: i.id,
timestamp: i.timestamp,
data: i
}))
].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
return NextResponse.json({ ok: true, thread });
} catch (error) {
console.error('[API/Sessions/Conversation] Failed:', error);
return NextResponse.json({ ok: false, error: String(error) }, { status: 500 });
}
}

View file

@ -0,0 +1,18 @@
import { NextResponse } from 'next/server';
import { ackAgentMessage } from '../../../../../../../lib/agent-mail';
export async function POST(
request: Request,
{ params }: { params: Promise<{ beadId: string; messageId: string }> }
): Promise<Response> {
const { messageId } = await params;
const url = new URL(request.url);
const agentId = url.searchParams.get('agent');
if (!agentId) {
return NextResponse.json({ ok: false, error: 'agent param required' }, { status: 400 });
}
const result = await ackAgentMessage({ agent: agentId, message: messageId });
return NextResponse.json(result);
}

View file

@ -0,0 +1,18 @@
import { NextResponse } from 'next/server';
import { readAgentMessage } from '../../../../../../../lib/agent-mail';
export async function POST(
request: Request,
{ params }: { params: Promise<{ beadId: string; messageId: string }> }
): Promise<Response> {
const { messageId } = await params;
const url = new URL(request.url);
const agentId = url.searchParams.get('agent');
if (!agentId) {
return NextResponse.json({ ok: false, error: 'agent param required' }, { status: 400 });
}
const result = await readAgentMessage({ agent: agentId, message: messageId });
return NextResponse.json(result);
}

View file

@ -0,0 +1,33 @@
import { NextResponse } from 'next/server';
import { readIssuesFromDisk } from '../../../lib/read-issues';
import { activityEventBus } from '../../../lib/realtime';
import { buildSessionTaskFeed, getCommunicationSummary } from '../../../lib/agent-sessions';
export const dynamic = 'force-dynamic';
export async function GET(request: Request): Promise<Response> {
const url = new URL(request.url);
const projectRoot = url.searchParams.get('projectRoot') ?? process.cwd();
try {
const issues = await readIssuesFromDisk({ projectRoot, preferBd: true });
const activity = activityEventBus.getHistory(projectRoot);
const communication = await getCommunicationSummary();
const feed = buildSessionTaskFeed(issues, activity, communication);
return NextResponse.json({ ok: true, feed });
} catch (error) {
console.error('[API/Sessions] Failed to load session feed:', error);
return NextResponse.json(
{
ok: false,
error: {
classification: 'unknown',
message: error instanceof Error ? error.message : 'Failed to load session feed.',
},
},
{ status: 500 },
);
}
}