import { useActionMutation, useActionQuery, } from "@agent-native/core/client/hooks"; import { IconAlertTriangle, IconCalendarTime, IconCheck, IconDatabase, IconFileDiff, IconPlayerPlay, IconRefresh, IconSettings, IconX, } from "@tabler/icons-react"; import { useEffect, useMemo, useState } from "react"; import { useSearchParams } from "react-router"; import { toast } from "sonner"; import { DispatchShell } from "../../components/dispatch-shell"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "../../components/ui/accordion"; import { Alert, AlertDescription, AlertTitle } from "../../components/ui/alert"; import { Badge } from "../../components/ui/badge"; import { Button } from "../../components/ui/button"; import { Input } from "../../components/ui/input"; import { Label } from "../../components/ui/label"; import { ScrollArea } from "../../components/ui/scroll-area"; import { Separator } from "../../components/ui/separator"; import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, } from "../../components/ui/sheet"; import { Skeleton } from "../../components/ui/skeleton"; import { Spinner } from "../../components/ui/spinner"; import { Switch } from "../../components/ui/switch"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger, } from "../../components/ui/tabs"; import { Textarea } from "../../components/ui/textarea"; import { cn } from "../../lib/utils"; import { dreamSettingsToDraft, dreamSettingsUpdateFromDraft, splitSourceIds, type DreamSettings, type DreamSettingsDraft, } from "./dream-settings"; export function meta() { return [{ title: "Dreams — Dispatch" }]; } type DreamStatus = | "running" | "completed" | "failed" | "pending" | "applied" | "rejected" | "stale" | string; interface DreamPass { id: string; title?: string | null; summary?: string | null; status?: DreamStatus | null; sourceId?: string | null; query?: string | null; error?: string | null; createdAt?: number | string | null; startedAt?: number | string | null; completedAt?: number | string | null; updatedAt?: number | string | null; candidateCount?: number | null; inspectedThreadCount?: number | null; inspectedRunCount?: number | null; proposalCount?: number | null; proposalCounts?: Record | null; appliedCount?: number | null; rejectedCount?: number | null; sourceHealth?: DreamSourceHealth[] | null; } interface DreamEvidence { id?: string | null; label?: string | null; title?: string | null; source?: string | null; sourceId?: string | null; threadId?: string | null; threadTitle?: string | null; runId?: string | null; kind?: string | null; quote?: string | null; snippet?: string | null; summary?: string | null; confidence?: number | null; createdAt?: number | string | null; [key: string]: unknown; } interface DreamProposal { id: string; dreamId?: string | null; title?: string | null; summary?: string | null; status?: DreamStatus | null; targetType?: string | null; targetPath?: string | null; type?: string | null; target?: string | null; path?: string | null; risk?: string | null; confidence?: number | null; rationale?: string | null; content?: string | null; evidence?: DreamEvidence[] | null; sourceRunIds?: string[] | null; createdAt?: number | string | null; } interface CandidateRun { id?: string; thread?: { id: string; ownerEmail: string; title: string; preview: string; messageCount: number; createdAt: number; updatedAt: number; }; title?: string | null; summary?: string | null; preview?: string | null; ownerEmail?: string | null; sourceId?: string | null; sourceLabel?: string | null; threadId?: string | null; runId?: string | null; status?: string | null; score?: number | null; reasons?: | string[] | Array<{ code: string; label: string; score: number; evidenceCount: number; }> | null; signals?: string[] | null; latestRunStatus?: string | null; updatedAt?: number | string | null; startedAt?: number | string | null; completedAt?: number | string | null; evidence?: DreamEvidence[] | null; } interface DreamSourceHealth { sourceId: string; label?: string | null; status: "ok" | "timed_out" | "error" | string; startedAt?: number | string | null; completedAt?: number | string | null; durationMs: number; timeoutMs?: number | null; inspectedThreadCount: number; candidateCount: number; errorCount: number; threadErrorCount?: number | null; message?: string | null; } interface DreamDetail { dream?: DreamPass | null; report?: string | null; summary?: string | null; proposals?: DreamProposal[] | null; candidates?: CandidateRun[] | null; inspectedRuns?: CandidateRun[] | null; evidence?: DreamEvidence[] | null; [key: string]: unknown; } type ListDreamsResponse = | DreamPass[] | { dreams?: DreamPass[]; items?: DreamPass[]; results?: DreamPass[]; }; type ListCandidatesResponse = | CandidateRun[] | { candidates?: CandidateRun[]; items?: CandidateRun[]; results?: CandidateRun[]; sources?: DreamSourceHealth[]; sourceHealth?: DreamSourceHealth[]; }; type GetDreamResponse = DreamDetail | null; interface CreateDreamReportParams { sourceId?: string; sourceIds?: string[]; allSources?: boolean; query?: string; ownerEmail?: string; limit?: number; sourceTimeoutMs?: number; sourceConcurrency?: number; sourceStartStaggerMs?: number; threadConcurrency?: number; threadTimeoutMs?: number; title?: string; } interface CreateDreamReportResult { id?: string; dreamId?: string; dream?: DreamPass; } interface ProposalMutationParams { id: string; reason?: string; } interface DreamProposalPreview { operation?: "create" | "update" | "append" | string; targetExists?: boolean; currentContent?: string | null; proposedContent?: string | null; target?: { type?: string | null; path?: string | null; kind?: string | null; resourceId?: string | null; }; approval?: { required?: boolean; policyEnabled?: boolean; willRequestApproval?: boolean; }; } function normalizeArray(value: unknown, keys: readonly string[]): T[] { if (Array.isArray(value)) return value as T[]; if (!value || typeof value !== "object") return []; const record = value as Record; for (const key of keys) { if (Array.isArray(record[key])) return record[key] as T[]; } return []; } function normalizeSourceHealth(value: unknown): DreamSourceHealth[] { if (!value || typeof value !== "object" || Array.isArray(value)) return []; const record = value as Record; if (Array.isArray(record.sources)) { return record.sources as DreamSourceHealth[]; } if (Array.isArray(record.sourceHealth)) { return record.sourceHealth as DreamSourceHealth[]; } return []; } function formatDate(value: number | string | null | undefined): string { if (value == null || value === "") return "n/a"; const numeric = Number(value); const date = Number.isFinite(numeric) ? new Date(numeric) : new Date(value); if (Number.isNaN(date.getTime())) return "n/a"; return date.toLocaleString(); } function compactDate(value: number | string | null | undefined): string { if (value == null || value === "") return "n/a"; const numeric = Number(value); const date = Number.isFinite(numeric) ? new Date(numeric) : new Date(value); if (Number.isNaN(date.getTime())) return "n/a"; return date.toLocaleDateString(undefined, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", }); } function json(value: unknown): string { try { return JSON.stringify(value, null, 2); } catch { return String(value); } } function plural(value: number, singular: string, pluralLabel = `${singular}s`) { return `${value} ${value === 1 ? singular : pluralLabel}`; } function dreamLabel(dream: DreamPass, index: number): string { return dream.title || `Dream pass ${index + 1}`; } function proposalTarget(proposal: DreamProposal): string { return ( proposal.targetPath || proposal.path || proposal.target || proposal.targetType || proposal.type || "memory" ); } function evidenceLabel(evidence: DreamEvidence, index: number): string { return ( evidence.label || evidence.title || evidence.threadTitle || evidence.source || evidence.threadId || evidence.runId || `Evidence ${index + 1}` ); } function candidateLabel(candidate: CandidateRun): string { return ( candidate.thread?.title || candidate.title || candidate.summary || candidate.thread?.preview || candidate.preview || candidate.thread?.id || candidate.threadId || candidate.runId || candidate.id || "candidate" ); } function candidateSignals(candidate: CandidateRun): string[] { const reasons = (candidate.reasons ?? []).map((reason) => typeof reason === "string" ? reason : reason.label, ); return [...reasons, ...(candidate.signals ?? [])].filter(Boolean); } function candidateId(candidate: CandidateRun): string { return ( candidate.id || candidate.thread?.id || candidate.threadId || candidate.runId || candidateLabel(candidate) ); } function candidateStatus(candidate: CandidateRun): string { return candidate.latestRunStatus || candidate.status || "unknown"; } function candidateOwner(candidate: CandidateRun): string { return candidate.thread?.ownerEmail || candidate.ownerEmail || "n/a"; } function candidateUpdatedAt(candidate: CandidateRun): number | string | null { return ( candidate.updatedAt || candidate.completedAt || candidate.startedAt || candidate.thread?.updatedAt || null ); } function dreamProposalCount(dream: DreamPass): number { return dream.proposalCount ?? dream.proposalCounts?.total ?? 0; } function dreamInspectedCount(dream: DreamPass): number { return dream.inspectedThreadCount ?? dream.inspectedRunCount ?? 0; } function resultDreamId(result: CreateDreamReportResult | null | undefined) { return result?.dream?.id || result?.dreamId || result?.id || null; } function statusVariant(status: DreamStatus | null | undefined) { const normalized = String(status || "pending").toLowerCase(); if (normalized === "failed") return "destructive" as const; if (normalized === "completed" || normalized === "applied") return "default" as const; if (normalized === "rejected" || normalized === "stale") return "outline" as const; return "secondary" as const; } function sourceStatusVariant(status: string | null | undefined) { const normalized = String(status || "ok").toLowerCase(); if (normalized === "error" || normalized === "timed_out") { return "destructive" as const; } return "secondary" as const; } function StatusBadge({ status }: { status?: DreamStatus | null }) { const normalized = String(status || "pending").toLowerCase(); return ( {normalized.replace(/_/g, " ")} ); } function SourceHealthPanel({ sources }: { sources: DreamSourceHealth[] }) { if (sources.length === 0) return null; const unhealthyCount = sources.filter( (source) => String(source.status).toLowerCase() !== "ok", ).length; return ( 0 ? "destructive" : "default"}> Source health
{sources.map((source) => ( {source.label || source.sourceId}:{" "} {String(source.status).replace(/_/g, " ")} · {source.durationMs} ms ))}
); } function isApprovalRequestResult(value: unknown): boolean { if (!value || typeof value !== "object") return false; const record = value as Record; const result = record.result as Record | undefined; return result?.approvalRequired === true; } function QueryState({ error, label }: { error: unknown; label: string }) { if (!error) return null; return ( {label} {error instanceof Error ? error.message : String(error)} ); } function RawBlock({ value }: { value: unknown }) { return (
      {typeof value === "string" ? value : json(value)}
    
); } function EmptyPanel({ title, description, }: { title: string; description: string; }) { return (
{title}
{description}
); } function DreamListSkeleton() { return (
{Array.from({ length: 5 }).map((_, index) => (
))}
); } function ProposalSkeleton() { return (
{Array.from({ length: 3 }).map((_, index) => (
))}
); } function DreamSettingsSheet({ open, onOpenChange, draft, onDraftChange, onSave, saving, loading, }: { open: boolean; onOpenChange: (open: boolean) => void; draft: DreamSettingsDraft; onDraftChange: (draft: DreamSettingsDraft) => void; onSave: () => void; saving: boolean; loading: boolean; }) { const sourceIds = splitSourceIds(draft.sourceIdsText); const canSave = draft.schedule.trim().length > 0; function update( key: K, value: DreamSettingsDraft[K], ) { onDraftChange({ ...draft, [key]: value }); } return (
{draft.enabled ? "Enabled" : "Paused"} {draft.schedule || "No schedule"}
Dream settings Configure recurring dream scope, schedule, and scan limits.
Schedule
Saved setting used by dream jobs.
update("enabled", checked)} />
update("schedule", event.target.value)} placeholder="0 9 * * 1" className="font-mono" />
update("minCandidateCount", event.target.value) } />
Sources
Scan every connected thread-debug source.
update("allSources", checked)} />
update("sourceId", event.target.value)} disabled={draft.allSources || sourceIds.length > 0} placeholder="current" className="font-mono" />
update("query", event.target.value)} placeholder="Optional search term" />