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:
parent
28abfe3ce2
commit
f3558dc0d1
13 changed files with 1153 additions and 0 deletions
22
src/app/api/sessions/[beadId]/comment/route.ts
Normal file
22
src/app/api/sessions/[beadId]/comment/route.ts
Normal 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 });
|
||||
}
|
||||
}
|
||||
58
src/app/api/sessions/[beadId]/conversation/route.ts
Normal file
58
src/app/api/sessions/[beadId]/conversation/route.ts
Normal 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 });
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
33
src/app/api/sessions/route.ts
Normal file
33
src/app/api/sessions/route.ts
Normal 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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue