feat(ux): consolidate Launch Swarm + telemetry UX with minimized strip

- Removed broken LaunchSwarmDialog (formula-based) from TopBar/LeftPanel
- All Rocket buttons (TopBar, LeftPanel, DAG nodes, social cards) now open
  AssignmentPanel (archetype-based) which actually works
- Every Rocket clears taskId first so assignMode && !taskId condition passes
- Conversation button priority: taskId always shows conversation, not assign panel
- Added TelemetryStrip: minimized right sidebar with status dots when non-telemetry
  panel (conversation/assignment) is active
- Live feed has minimize button → restores last taskId or assignMode
- DAG nodes: Signal icon → restores telemetry feed
- Social button on DAG nodes: single router.push to avoid race (setView + setTaskId)
- Fixed social card message button: opens right panel with drawer:closed (no popup)

Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
zenchantlive 2026-03-01 18:17:58 -08:00
parent 65d69ecbbc
commit c246ceaf21
165 changed files with 13730 additions and 1132 deletions

View file

@ -0,0 +1,37 @@
import { NextResponse } from 'next/server';
import { runBdCommand } from '../../../../lib/bridge';
export const dynamic = 'force-dynamic';
export async function GET(request: Request): Promise<Response> {
const { searchParams } = new URL(request.url);
const projectRoot = searchParams.get('projectRoot') ?? process.cwd();
const result = await runBdCommand({
projectRoot,
args: ['--version'],
timeoutMs: 8_000,
});
if (!result.success) {
const status = result.classification === 'not_found' ? 503 : 500;
return NextResponse.json(
{
ok: false,
error: {
classification: result.classification ?? 'unknown',
message: result.error ?? result.stderr ?? 'bd health check failed',
},
},
{ status },
);
}
return NextResponse.json({
ok: true,
data: {
version: result.stdout,
},
});
}

View file

@ -29,7 +29,7 @@ export async function handleMutationRequest(request: Request, operation: Mutatio
const payload = validateMutationPayload(operation, body);
const result = await executeMutation(operation, payload);
const status = result.ok ? 200 : result.error?.classification === 'not_found' ? 404 : 400;
const status = result.ok ? 200 : result.error?.classification === 'not_found' ? 503 : 400;
return NextResponse.json(result, { status });
} catch (error) {
if (error instanceof MutationValidationError) {

View file

@ -0,0 +1,56 @@
import { NextResponse } from 'next/server';
import { writeCoordEvent } from '../../../../lib/coord-events';
interface CoordEventsDeps {
writeCoordEvent: typeof writeCoordEvent;
}
function parseBody(data: unknown): { projectRoot: string; event: unknown } | null {
if (!data || typeof data !== 'object') return null;
const record = data as Record<string, unknown>;
if (typeof record.projectRoot !== 'string' || !record.projectRoot.trim()) return null;
return {
projectRoot: record.projectRoot.trim(),
event: record.event,
};
}
export async function handleCoordEventsPost(
request: Request,
deps?: Partial<CoordEventsDeps>,
): Promise<Response> {
let body: unknown;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ ok: false, error: { classification: 'bad_args', message: 'Invalid JSON body' } },
{ status: 400 },
);
}
const parsed = parseBody(body);
if (!parsed) {
return NextResponse.json(
{ ok: false, error: { classification: 'bad_args', message: 'projectRoot and event are required' } },
{ status: 400 },
);
}
const writer = deps?.writeCoordEvent ?? writeCoordEvent;
const result = await writer(parsed.event, { projectRoot: parsed.projectRoot });
if (!result.ok) {
const status = result.error.classification === 'bad_args' ? 400 : 500;
return NextResponse.json(
{ ok: false, error: { classification: result.error.classification, message: result.error.message } },
{ status },
);
}
return NextResponse.json({ ok: true, eventId: result.eventId });
}
export async function POST(request: Request): Promise<Response> {
return handleCoordEventsPost(request);
}

View file

@ -22,7 +22,7 @@ export async function GET(
const activity = history.filter((e: ActivityEvent) => e.beadId === beadId);
// 2. Get communication for this bead
const summary = await getCommunicationSummary();
const summary = await getCommunicationSummary(projectRoot);
const messages = summary.messages.filter((m: AgentMessage) => m.bead_id === beadId);
// 3. Get local bd interactions via CLI
@ -55,4 +55,4 @@ export async function GET(
console.error('[API/Sessions/Conversation] Failed:', error);
return NextResponse.json({ ok: false, error: String(error) }, { status: 500 });
}
}
}

View file

@ -41,9 +41,9 @@ export async function GET(request: Request): Promise<Response> {
try {
const issues = await readIssuesFromDisk({ projectRoot, preferBd: true });
const activity = activityEventBus.getHistory(projectRoot);
const communication = await getCommunicationSummary();
const communication = await getCommunicationSummary(projectRoot);
const livenessMap = await getAgentLivenessMap(projectRoot, activity);
const incursions = await calculateIncursions();
const incursions = await calculateIncursions(projectRoot, livenessMap);
const agentsResult = await listAgents({}, { projectRoot });
const feed = buildSessionTaskFeed(issues, activity, communication, livenessMap);

View file

@ -21,9 +21,10 @@ export async function GET(request: Request): Promise<Response> {
});
if (!result.success) {
const status = result.classification === 'not_found' ? 503 : 400;
return NextResponse.json(
{ ok: false, error: { classification: result.classification ?? 'unknown', message: result.error ?? result.stderr } },
{ status: 400 },
{ status },
);
}

View file

@ -29,9 +29,10 @@ export async function GET(request: Request): Promise<Response> {
});
if (!result.success) {
const status = result.classification === 'not_found' ? 503 : 400;
return NextResponse.json(
{ ok: false, error: { classification: result.classification ?? 'unknown', message: result.error ?? result.stderr } },
{ status: 400 },
{ status },
);
}