Intelligence
Artifacts
Browse the repository, read documents, and manage the governance folders. Source, runtime, and infrastructure are read-only.
Repository
repositories/aaf-holdings/hq01/app/mission-control/[id]/assignments/[aid]/page.tsx
12.9 KB
import Link from "next/link";
import { notFound } from "next/navigation";
import { Layers, History as HistoryIcon, FileCode, HardHat } from "lucide-react";
import { PageHeader } from "@/components/layout/page-header";
import { Card, CardContent } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { MetaItem, FieldList } from "@/components/shared/field-list";
import { PriorityBadge } from "@/components/shared/status-badge";
import { AssignmentStatusBadge } from "@/components/missions/assignment-status-badge";
import { ReportList } from "@/components/missions/report-list";
import { AssetList } from "@/components/assets/asset-list";
import { WorkerInstantiate } from "@/components/workers/worker-instantiate";
import { AutoRefresh } from "@/components/sessions/auto-refresh";
import {
getAssignmentView,
collectAssignmentReports,
} from "@/lib/missions/assignments";
import { listReportsForAssignment } from "@/lib/missions/reports";
import { listAssetsForAssignment } from "@/lib/assets/ledger";
import { listTemplates } from "@/lib/workers/templates";
import { listInstancesForAssignment, instanceHistory } from "@/lib/workers/instances";
import { getMission, collectReports } from "@/lib/missions/manager";
import type { ExecChainEventType } from "@/lib/missions/types";
export const dynamic = "force-dynamic";
const EVENT_LABEL: Partial<Record<ExecChainEventType, string>> = {
assignment_created: "Assignment created",
execution_context_created: "Execution context created",
dispatch_started: "Dispatch started",
dispatch_completed: "Dispatch completed",
};
export default function AssignmentDetailPage({
params,
}: {
params: { id: string; aid: string };
}) {
// Reconcile completed sessions into reports + assignment status, then read.
collectReports(params.id);
collectAssignmentReports(params.id);
const view = getAssignmentView(params.id, params.aid);
if (!view) notFound();
const { assignment, execution_context: ctx, history } = view;
const mission = getMission(params.id);
const reports = listReportsForAssignment(params.id, params.aid);
const assets = listAssetsForAssignment(params.aid);
const templates = listTemplates().map((t) => ({ id: t.id, name: t.name }));
const workers = listInstancesForAssignment(params.aid);
const currentWorker = assignment.worker_instance_id
? workers.find((w) => w.id === assignment.worker_instance_id) ?? null
: null;
const currentWorkerHistory = currentWorker ? instanceHistory(currentWorker.id) : [];
return (
<div>
<AutoRefresh intervalMs={5000} />
<PageHeader
eyebrow={`${assignment.mission_id} · ${assignment.work_order_id} · ${assignment.id}`}
title={`Assignment ${assignment.id.replace("ASSIGNMENT-", "A-")}`}
description={`Delegated to ${assignment.executive}.`}
back={{
label: mission ? mission.title : "Mission",
href: `/mission-control/${assignment.mission_id}`,
}}
actions={
<div className="flex flex-col items-end gap-2">
<div className="flex items-center gap-2">
<PriorityBadge priority={assignment.priority} />
<AssignmentStatusBadge status={assignment.status} />
</div>
<WorkerInstantiate
missionId={assignment.mission_id}
aid={assignment.id}
templates={templates}
status={assignment.status}
/>
</div>
}
/>
<div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
<div className="space-y-6 lg:col-span-2">
<Card>
<CardContent className="p-6">
<div className="mb-4 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground">
<Layers className="h-3.5 w-3.5" /> Execution Context
<span className="font-normal lowercase tracking-normal text-muted-foreground/70">
(immutable)
</span>
</div>
{!ctx ? (
<p className="text-sm text-muted-foreground">No execution context.</p>
) : (
<div className="grid grid-cols-1 gap-x-6 gap-y-3 sm:grid-cols-2">
<Ctx label="Organization">{ctx.organization}</Ctx>
<Ctx label="Company">{ctx.company ?? "—"}</Ctx>
<Ctx label="Product">{ctx.product}</Ctx>
<Ctx label="Repository" mono>{ctx.repository}</Ctx>
<Ctx label="Mission" mono>{ctx.mission_id}</Ctx>
<Ctx label="Objective" mono>{ctx.objective_id}</Ctx>
<Ctx label="Work Order" mono>{ctx.work_order_id}</Ctx>
<Ctx label="Assignment" mono>{ctx.assignment_id}</Ctx>
<Ctx label="Executive" mono>{ctx.executive}</Ctx>
<Ctx label="Workspace" mono full>{ctx.workspace}</Ctx>
<Ctx label="Runtime Root" mono full>{ctx.runtime_root}</Ctx>
<Ctx label="Report Location" mono full>{ctx.report_location}</Ctx>
<Ctx label="Allowed Paths" mono full>{ctx.allowed_paths.join(", ")}</Ctx>
<Ctx label="Expected Outputs" full>{ctx.expected_outputs.join(", ")}</Ctx>
</div>
)}
{ctx && ctx.success_criteria.length > 0 && (
<div className="mt-5">
<FieldList label="Success Criteria" items={ctx.success_criteria} />
</div>
)}
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="mb-3 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground">
<HardHat className="h-3.5 w-3.5" /> Workers ({workers.length})
</div>
{currentWorker && (
<div className="mb-3 rounded-md border border-border p-3">
<div className="flex flex-wrap items-center gap-2 text-[13px]">
<span className="font-mono text-[11px] text-muted-foreground">
{currentWorker.id.replace("WORKER-", "W-")}
</span>
<Link href={`/workers/${currentWorker.template_id}`} className="font-medium text-foreground hover:underline">
{currentWorker.template_id} v{currentWorker.version}
</Link>
<span className="rounded-full bg-secondary px-2 py-0.5 text-[10px] font-medium text-foreground/70">
{currentWorker.status}
</span>
<span className="font-mono text-[11px] text-muted-foreground">{currentWorker.model}</span>
</div>
<div className="mt-1 text-[11px] text-muted-foreground">
{currentWorker.session_id && (
<Link href={`/sessions/${currentWorker.session_id}`} className="underline underline-offset-2">session</Link>
)}
{currentWorker.report_id && <> · report {currentWorker.report_id.replace("REPORT-", "R-")}</>}
</div>
<ol className="mt-2 space-y-0.5 border-l border-border pl-3">
{currentWorkerHistory.map((e, i) => (
<li key={i} className="text-[11px] text-muted-foreground">
<span className="font-medium text-foreground/70">{e.type.replace(/_/g, " ")}</span>
{e.detail ? ` — ${e.detail}` : ""} · {new Date(e.at).toLocaleString()}
</li>
))}
</ol>
</div>
)}
{workers.length === 0 ? (
<p className="text-sm text-muted-foreground">
No worker instantiated yet. Instantiate one above; it runs and
terminates — no worker persists.
</p>
) : (
<ul className="divide-y divide-border text-[12px]">
{workers.filter((w) => w.id !== currentWorker?.id).map((w) => (
<li key={w.id} className="flex items-center gap-2 py-1.5">
<span className="font-mono text-[11px] text-muted-foreground">{w.id.replace("WORKER-", "W-")}</span>
<span className="flex-1">{w.template_id}</span>
<span className="text-muted-foreground">{w.status}</span>
</li>
))}
</ul>
)}
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="mb-3 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground">
<FileCode className="h-3.5 w-3.5" /> Reports ({reports.length})
</div>
<ReportList reports={reports} emptyText="No reports yet for this assignment." />
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="mb-3 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground">
<Layers className="h-3.5 w-3.5" /> Produced Assets ({assets.length})
</div>
<AssetList assets={assets} showMission={false} emptyText="No assets registered from this assignment." />
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="mb-4 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground">
<HistoryIcon className="h-3.5 w-3.5" /> History
</div>
<ol className="space-y-3">
{history.map((e, i) => (
<li key={i} className="flex gap-3">
<div className="mt-1 h-1.5 w-1.5 shrink-0 rounded-full bg-accent" />
<div>
<div className="text-[13px] font-medium text-foreground">
{EVENT_LABEL[e.type] ?? e.type}
{e.detail && (
<span className="font-normal text-muted-foreground"> — {e.detail}</span>
)}
</div>
<div className="text-[11px] text-muted-foreground">
{new Date(e.at).toLocaleString()}
{e.session_id && (
<>
{" · "}
<Link
href={`/sessions/${e.session_id}`}
className="font-mono underline underline-offset-2"
>
session
</Link>
</>
)}
</div>
</div>
</li>
))}
</ol>
</CardContent>
</Card>
</div>
<aside className="space-y-6">
<Card>
<CardContent className="space-y-5 p-6">
<MetaItem label="Status">
<AssignmentStatusBadge status={assignment.status} />
</MetaItem>
<Separator />
<MetaItem label="Worker Template">
{assignment.worker_template ?? "— (placeholder; no workers yet)"}
</MetaItem>
{assignment.session && (
<>
<Separator />
<MetaItem label="Session">
<Link
href={`/sessions/${assignment.session.session_id}`}
className="inline-flex items-center gap-1.5 font-mono text-[12px] underline underline-offset-2"
>
<FileCode className="h-3.5 w-3.5" />
{assignment.session.session_id.slice(0, 22)}…
</Link>
<div className="mt-1 text-[11px] text-muted-foreground">
{assignment.session.status}
</div>
</MetaItem>
</>
)}
</CardContent>
</Card>
<div className="font-mono text-[11px] text-muted-foreground">
/srv/aaf/missions/{assignment.mission_id}/assignments/{assignment.id}/
</div>
</aside>
</div>
</div>
);
}
function Ctx({
label,
children,
mono,
full,
}: {
label: string;
children: React.ReactNode;
mono?: boolean;
full?: boolean;
}) {
return (
<div className={full ? "sm:col-span-2" : ""}>
<div className="text-[10px] font-medium uppercase tracking-[0.1em] text-muted-foreground">
{label}
</div>
<div
className={
"mt-0.5 break-all text-[13px] text-foreground" + (mono ? " font-mono text-[12px]" : "")
}
>
{children}
</div>
</div>
);
}
root · /srv/aaf