@invergent/agent-chat-react 1.8.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { cn } from './chunk-QSC4UIVT.js';
2
2
  import { createContext, memo, useRef, useEffect, useCallback, useMemo, useState, lazy, useContext, Suspense, Children } from 'react';
3
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, EyeIcon, GlobeIcon, CheckIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, InboxIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
3
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, ChevronRightIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, FolderOpenIcon, Loader2Icon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, CheckIcon, CopyIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, SearchIcon, GitBranchIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, EyeIcon, GlobeIcon, ListTodoIcon, CornerDownLeftIcon, XCircleIcon, CheckCircle2Icon, Code, TerminalIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
4
4
  import { cva } from 'class-variance-authority';
5
- import { Collapsible as Collapsible$1, Slot, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Tooltip as Tooltip$1, Tabs as Tabs$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1, HoverCard as HoverCard$1, Progress as Progress$1 } from 'radix-ui';
5
+ import { Collapsible as Collapsible$1, Slot, Tooltip as Tooltip$1, Separator as Separator$1, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1, HoverCard as HoverCard$1, Progress as Progress$1, Tabs as Tabs$1 } from 'radix-ui';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
  import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
8
8
  import { cjk } from '@streamdown/cjk';
@@ -597,6 +597,26 @@ var ConversationScrollButton = ({
597
597
  }
598
598
  );
599
599
  };
600
+ function Separator({
601
+ className,
602
+ orientation = "horizontal",
603
+ decorative = true,
604
+ ...props
605
+ }) {
606
+ return /* @__PURE__ */ jsx(
607
+ Separator$1.Root,
608
+ {
609
+ "data-slot": "separator",
610
+ decorative,
611
+ orientation,
612
+ className: cn(
613
+ "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
614
+ className
615
+ ),
616
+ ...props
617
+ }
618
+ );
619
+ }
600
620
  var Message = ({ className, from, ...props }) => /* @__PURE__ */ jsx(
601
621
  "div",
602
622
  {
@@ -4205,32 +4225,28 @@ function WebSearchGroupBlock({ tcs }) {
4205
4225
  }
4206
4226
  var ChatMessage = memo(function ChatMessage2({
4207
4227
  message,
4208
- isLast,
4209
4228
  onFileSelect
4210
4229
  }) {
4211
- if (message.role === "user") {
4212
- return /* @__PURE__ */ jsx(Message, { from: "user", children: /* @__PURE__ */ jsxs(MessageContent, { children: [
4213
- message.content,
4214
- message.images && message.images.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mt-2", children: message.images.map((img, i) => /* @__PURE__ */ jsx(
4215
- "img",
4216
- {
4217
- src: img.data,
4218
- alt: "Attached",
4219
- className: "max-w-64 max-h-48 rounded-lg border border-border object-contain"
4220
- },
4221
- i
4222
- )) }),
4223
- message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mt-2", children: message.attachments.map((a, i) => /* @__PURE__ */ jsx(
4224
- AttachmentChip,
4225
- {
4226
- attachment: a,
4227
- onSelect: onFileSelect
4228
- },
4229
- a.path ?? `pending-${i}`
4230
- )) })
4231
- ] }) });
4232
- }
4233
- return /* @__PURE__ */ jsx(AssistantMessage, { message, isLast, onFileSelect });
4230
+ return /* @__PURE__ */ jsx(Message, { from: "user", children: /* @__PURE__ */ jsxs(MessageContent, { children: [
4231
+ message.content,
4232
+ message.images && message.images.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mt-2", children: message.images.map((img, i) => /* @__PURE__ */ jsx(
4233
+ "img",
4234
+ {
4235
+ src: img.data,
4236
+ alt: "Attached",
4237
+ className: "max-w-64 max-h-48 rounded-lg border border-border object-contain"
4238
+ },
4239
+ i
4240
+ )) }),
4241
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mt-2", children: message.attachments.map((a, i) => /* @__PURE__ */ jsx(
4242
+ AttachmentChip,
4243
+ {
4244
+ attachment: a,
4245
+ onSelect: onFileSelect
4246
+ },
4247
+ a.path ?? `pending-${i}`
4248
+ )) })
4249
+ ] }) });
4234
4250
  });
4235
4251
  function iconForMime(mime) {
4236
4252
  if (!mime) return File$1;
@@ -4285,28 +4301,6 @@ function AttachmentChip({
4285
4301
  }
4286
4302
  );
4287
4303
  }
4288
- function AssistantMessage({
4289
- message,
4290
- isLast,
4291
- onFileSelect
4292
- }) {
4293
- const hasReasoning = !!message.reasoning;
4294
- const hasToolCalls = !!(message.toolCalls && message.toolCalls.length > 0);
4295
- const isStreaming = message.status === "streaming" && isLast;
4296
- const hasContent = !!(message.content && message.content !== message.reasoning);
4297
- return /* @__PURE__ */ jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsxs(MessageContent, { children: [
4298
- hasReasoning && /* @__PURE__ */ jsxs(Reasoning, { isStreaming: isStreaming && !hasContent && !hasToolCalls, children: [
4299
- /* @__PURE__ */ jsx(ReasoningTrigger, {}),
4300
- /* @__PURE__ */ jsx(ReasoningContent, { children: message.reasoning })
4301
- ] }),
4302
- hasToolCalls && /* @__PURE__ */ jsx("div", { className: "w-full flex flex-col gap-8", children: message.toolCalls.map((tc) => /* @__PURE__ */ jsx(ToolCallBlock, { tc, onFileSelect }, tc.id)) }),
4303
- hasContent && /* @__PURE__ */ jsx(MessageResponse, { children: message.content }),
4304
- isStreaming && !hasContent && !hasToolCalls && !hasReasoning && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 py-1 text-muted-foreground", children: [
4305
- /* @__PURE__ */ jsx("span", { className: "inline-block size-1.5 animate-pulse rounded-full bg-primary" }),
4306
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: "Thinking..." })
4307
- ] })
4308
- ] }) });
4309
- }
4310
4304
  function InputGroup({ className, ...props }) {
4311
4305
  return /* @__PURE__ */ jsx(
4312
4306
  "div",
@@ -5869,6 +5863,11 @@ function ChatComposerInner({
5869
5863
  { value: "/goal pause", label: "/goal pause", description: "Pause automatic goal continuation" },
5870
5864
  { value: "/goal resume", label: "/goal resume", description: "Resume a paused goal" },
5871
5865
  { value: "/goal clear", label: "/goal clear", description: "Clear the current goal" },
5866
+ { value: "/mission ", label: "/mission", description: "Start an orchestrated rubric-judged mission" },
5867
+ { value: "/mission status", label: "/mission status", description: "Show current mission status" },
5868
+ { value: "/mission pause", label: "/mission pause", description: "Pause the mission evaluator" },
5869
+ { value: "/mission resume", label: "/mission resume", description: "Resume a paused mission" },
5870
+ { value: "/mission cancel", label: "/mission cancel", description: "Cancel the mission" },
5872
5871
  { value: "/loop", label: "/loop", description: "Schedule recurring prompt" },
5873
5872
  { value: "/loop list", label: "/loop list", description: "List active loops" },
5874
5873
  { value: "/loop cancel", label: "/loop cancel", description: "Cancel a loop by ID" }
@@ -6174,6 +6173,54 @@ function ChatComposerInner({
6174
6173
  }
6175
6174
  );
6176
6175
  }
6176
+ var MIN_CPS = 40;
6177
+ var MAX_CPS = 240;
6178
+ var DRAIN_SECONDS = 0.25;
6179
+ var SNAP_LAG = 800;
6180
+ var MAX_FRAME_DT = 0.1;
6181
+ function useSmoothStream(target, isStreaming) {
6182
+ const [displayedLen, setDisplayedLen] = useState(
6183
+ () => isStreaming ? 0 : target.length
6184
+ );
6185
+ const targetRef = useRef(target);
6186
+ targetRef.current = target;
6187
+ useEffect(() => {
6188
+ setDisplayedLen((cur) => cur > target.length ? target.length : cur);
6189
+ }, [target]);
6190
+ const isActive = isStreaming || displayedLen < target.length;
6191
+ useEffect(() => {
6192
+ if (!isActive) return;
6193
+ let raf = 0;
6194
+ let carry = 0;
6195
+ let lastTime = 0;
6196
+ const tick = (now) => {
6197
+ const dt = lastTime ? Math.min(MAX_FRAME_DT, (now - lastTime) / 1e3) : 0;
6198
+ lastTime = now;
6199
+ setDisplayedLen((current) => {
6200
+ const targetLen = targetRef.current.length;
6201
+ const lag = targetLen - current;
6202
+ if (lag <= 0) return current;
6203
+ if (lag >= SNAP_LAG) {
6204
+ carry = 0;
6205
+ return targetLen;
6206
+ }
6207
+ const cps = Math.min(
6208
+ MAX_CPS,
6209
+ Math.max(MIN_CPS, lag / DRAIN_SECONDS)
6210
+ );
6211
+ carry += cps * dt;
6212
+ const step = Math.floor(carry);
6213
+ if (step <= 0) return current;
6214
+ carry -= step;
6215
+ return Math.min(targetLen, current + step);
6216
+ });
6217
+ raf = requestAnimationFrame(tick);
6218
+ };
6219
+ raf = requestAnimationFrame(tick);
6220
+ return () => cancelAnimationFrame(raf);
6221
+ }, [isActive]);
6222
+ return displayedLen >= target.length ? target : target.slice(0, displayedLen);
6223
+ }
6177
6224
  var Artifact = ({ className, ...props }) => /* @__PURE__ */ jsx(
6178
6225
  "div",
6179
6226
  {
@@ -6772,7 +6819,12 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
6772
6819
  }
6773
6820
  }
6774
6821
  if (effectiveHasContent) {
6775
- entries.push({ kind: "text", key: `${msg.id}-text`, content: msg.content });
6822
+ entries.push({
6823
+ kind: "text",
6824
+ key: `${msg.id}-text`,
6825
+ content: msg.content,
6826
+ isStreaming
6827
+ });
6776
6828
  }
6777
6829
  const hasRunningTool = hasToolCalls && msg.toolCalls.some(
6778
6830
  (tc) => tc.status === "running"
@@ -6952,16 +7004,7 @@ function TimelineEntryItem({
6952
7004
  onFileSelect
6953
7005
  }) {
6954
7006
  if (entry.kind === "reasoning") {
6955
- return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
6956
- /* @__PURE__ */ jsxs(TimelineHeader, { children: [
6957
- /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
6958
- /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2" })
6959
- ] }),
6960
- /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsxs(Reasoning, { isStreaming: entry.isStreaming, children: [
6961
- /* @__PURE__ */ jsx(ReasoningTrigger, {}),
6962
- /* @__PURE__ */ jsx(ReasoningContent, { children: entry.reasoning })
6963
- ] }) })
6964
- ] });
7007
+ return /* @__PURE__ */ jsx(ReasoningEntry, { entry, step });
6965
7008
  }
6966
7009
  if (entry.kind === "tool") {
6967
7010
  const rawStatus = effectiveStatus(entry.tc);
@@ -7013,13 +7056,7 @@ function TimelineEntryItem({
7013
7056
  ] });
7014
7057
  }
7015
7058
  if (entry.kind === "text") {
7016
- return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
7017
- /* @__PURE__ */ jsxs(TimelineHeader, { children: [
7018
- /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7019
- /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-foreground/40" })
7020
- ] }),
7021
- /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(MessageResponse, { children: entry.content }) })
7022
- ] });
7059
+ return /* @__PURE__ */ jsx(TextEntry, { entry, step });
7023
7060
  }
7024
7061
  if (entry.kind === "skill_invoked") {
7025
7062
  return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
@@ -7063,6 +7100,35 @@ function TimelineEntryItem({
7063
7100
  /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: "Working on it..." }) })
7064
7101
  ] });
7065
7102
  }
7103
+ function ReasoningEntry({
7104
+ entry,
7105
+ step
7106
+ }) {
7107
+ const reasoning = useSmoothStream(entry.reasoning, entry.isStreaming);
7108
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
7109
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
7110
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7111
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2" })
7112
+ ] }),
7113
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsxs(Reasoning, { isStreaming: entry.isStreaming, children: [
7114
+ /* @__PURE__ */ jsx(ReasoningTrigger, {}),
7115
+ /* @__PURE__ */ jsx(ReasoningContent, { children: reasoning })
7116
+ ] }) })
7117
+ ] });
7118
+ }
7119
+ function TextEntry({
7120
+ entry,
7121
+ step
7122
+ }) {
7123
+ const content = useSmoothStream(entry.content, entry.isStreaming);
7124
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
7125
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
7126
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7127
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-foreground/40" })
7128
+ ] }),
7129
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(MessageResponse, { children: content }) })
7130
+ ] });
7131
+ }
7066
7132
  function AssistantGroup({
7067
7133
  messages,
7068
7134
  lastGlobalIndex,
@@ -7209,7 +7275,6 @@ function ChatThread({
7209
7275
  ChatMessage,
7210
7276
  {
7211
7277
  message: msg,
7212
- isLast: group.lastGlobalIndex === messages.length - 1,
7213
7278
  onFileSelect
7214
7279
  },
7215
7280
  msg.id
@@ -10231,9 +10296,817 @@ function useInboxUnreadCount(adapter) {
10231
10296
  }, [adapter]);
10232
10297
  return { unreadCount, hasLoaded, error };
10233
10298
  }
10234
- var DEFAULT_LIMIT2 = 50;
10235
- var DEFAULT_POLL_INTERVAL_MS = 3e4;
10299
+ function Card({
10300
+ className,
10301
+ size = "default",
10302
+ ...props
10303
+ }) {
10304
+ return /* @__PURE__ */ jsx(
10305
+ "div",
10306
+ {
10307
+ "data-slot": "card",
10308
+ "data-size": size,
10309
+ className: cn(
10310
+ "group/card flex flex-col gap-8 overflow-hidden bg-card py-8 text-sm text-card-foreground shadow-sm ring-1 ring-foreground/5 has-[>img:first-child]:pt-0 data-[size=sm]:gap-5 data-[size=sm]:py-5 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none",
10311
+ className
10312
+ ),
10313
+ ...props
10314
+ }
10315
+ );
10316
+ }
10317
+ function CardHeader({ className, ...props }) {
10318
+ return /* @__PURE__ */ jsx(
10319
+ "div",
10320
+ {
10321
+ "data-slot": "card-header",
10322
+ className: cn(
10323
+ "group/card-header @container/card-header grid auto-rows-min items-start gap-1.5 rounded-none px-8 group-data-[size=sm]/card:px-5 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-8 group-data-[size=sm]/card:[.border-b]:pb-5",
10324
+ className
10325
+ ),
10326
+ ...props
10327
+ }
10328
+ );
10329
+ }
10330
+ function CardTitle({ className, ...props }) {
10331
+ return /* @__PURE__ */ jsx(
10332
+ "div",
10333
+ {
10334
+ "data-slot": "card-title",
10335
+ className: cn(
10336
+ "font-heading text-lg font-semibold tracking-wider uppercase",
10337
+ className
10338
+ ),
10339
+ ...props
10340
+ }
10341
+ );
10342
+ }
10343
+ function CardDescription({ className, ...props }) {
10344
+ return /* @__PURE__ */ jsx(
10345
+ "div",
10346
+ {
10347
+ "data-slot": "card-description",
10348
+ className: cn("text-sm leading-relaxed text-muted-foreground", className),
10349
+ ...props
10350
+ }
10351
+ );
10352
+ }
10353
+ function CardContent({ className, ...props }) {
10354
+ return /* @__PURE__ */ jsx(
10355
+ "div",
10356
+ {
10357
+ "data-slot": "card-content",
10358
+ className: cn("px-8 group-data-[size=sm]/card:px-5", className),
10359
+ ...props
10360
+ }
10361
+ );
10362
+ }
10363
+
10364
+ // src/components/missions/mission-derive.ts
10365
+ var ACTIVE_MISSION_STATUSES = /* @__PURE__ */ new Set(["active", "paused"]);
10366
+ function isTerminalMissionStatus(status) {
10367
+ return !ACTIVE_MISSION_STATUSES.has(status);
10368
+ }
10369
+ function missionTaskBucket(status) {
10370
+ if (status === "done") return "done";
10371
+ if (status === "blocked") return "blocked";
10372
+ if (status === "failed" || status === "cancelled") {
10373
+ return "failed_or_cancelled";
10374
+ }
10375
+ return "in_flight";
10376
+ }
10377
+ function groupMissionTasksByBucket(tasks) {
10378
+ const buckets = {
10379
+ in_flight: [],
10380
+ done: [],
10381
+ blocked: [],
10382
+ failed_or_cancelled: []
10383
+ };
10384
+ for (const t of tasks) {
10385
+ buckets[missionTaskBucket(t.status)].push(t);
10386
+ }
10387
+ return buckets;
10388
+ }
10389
+ function deriveMissionWorkerActivityLabel(worker) {
10390
+ const kind = worker.latestEventKind;
10391
+ if (kind === "tool.call" && worker.latestEventSummary) {
10392
+ return `tool: ${truncate2(worker.latestEventSummary, 80)}`;
10393
+ }
10394
+ if (kind === "llm.response") {
10395
+ return "thinking";
10396
+ }
10397
+ if (kind === "tool.result") {
10398
+ return "received tool result";
10399
+ }
10400
+ if (kind && worker.latestEventSummary) {
10401
+ return `${kind}: ${truncate2(worker.latestEventSummary, 60)}`;
10402
+ }
10403
+ return worker.sessionStatus || "active";
10404
+ }
10405
+ function truncate2(s, max) {
10406
+ if (s.length <= max) return s;
10407
+ return `${s.slice(0, max - 1)}\u2026`;
10408
+ }
10409
+ var DEFAULT_POLL_INTERVAL_MS = 5e3;
10410
+ var INITIAL_STATE = {
10411
+ mission: null,
10412
+ tasks: [],
10413
+ workers: [],
10414
+ loading: true,
10415
+ error: null
10416
+ };
10417
+ function requireMissionApi(adapter) {
10418
+ const required = {
10419
+ getMission: adapter.getMission,
10420
+ getMissionTasks: adapter.getMissionTasks,
10421
+ getMissionWorkers: adapter.getMissionWorkers,
10422
+ pauseMission: adapter.pauseMission,
10423
+ resumeMission: adapter.resumeMission,
10424
+ cancelMission: adapter.cancelMission
10425
+ };
10426
+ for (const [name, fn] of Object.entries(required)) {
10427
+ if (typeof fn !== "function") {
10428
+ throw new Error(
10429
+ `MissionDashboard requires adapter.${name} to be implemented; this adapter is missing the mission surface.`
10430
+ );
10431
+ }
10432
+ }
10433
+ return required;
10434
+ }
10435
+ function MissionDashboard({
10436
+ adapter,
10437
+ missionId,
10438
+ pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
10439
+ onNavigateBack,
10440
+ onOpenTranscript
10441
+ }) {
10442
+ const api = useMemo(() => requireMissionApi(adapter), [adapter]);
10443
+ const [state, setState] = useState(INITIAL_STATE);
10444
+ const [cancelOpen, setCancelOpen] = useState(false);
10445
+ const [cancelCascade, setCancelCascade] = useState(false);
10446
+ const [busy, setBusy] = useState(false);
10447
+ const isMounted = useRef(true);
10448
+ useEffect(() => {
10449
+ isMounted.current = true;
10450
+ return () => {
10451
+ isMounted.current = false;
10452
+ };
10453
+ }, []);
10454
+ const refresh = useCallback(async () => {
10455
+ try {
10456
+ const [mission2, tasksResp, workersResp] = await Promise.all([
10457
+ api.getMission({ missionId }),
10458
+ api.getMissionTasks({ missionId }),
10459
+ api.getMissionWorkers({ missionId })
10460
+ ]);
10461
+ if (!isMounted.current) return;
10462
+ setState({
10463
+ mission: mission2,
10464
+ tasks: tasksResp.tasks,
10465
+ workers: workersResp.workers,
10466
+ loading: false,
10467
+ error: null
10468
+ });
10469
+ } catch (err) {
10470
+ if (!isMounted.current) return;
10471
+ setState((prev) => ({
10472
+ ...prev,
10473
+ loading: false,
10474
+ error: err instanceof Error ? err.message : String(err)
10475
+ }));
10476
+ }
10477
+ }, [api, missionId]);
10478
+ useEffect(() => {
10479
+ void refresh();
10480
+ }, [refresh]);
10481
+ useEffect(() => {
10482
+ if (!state.mission) return;
10483
+ if (isTerminalMissionStatus(state.mission.status)) return;
10484
+ const id = window.setInterval(() => {
10485
+ void refresh();
10486
+ }, pollIntervalMs);
10487
+ return () => {
10488
+ window.clearInterval(id);
10489
+ };
10490
+ }, [refresh, state.mission, pollIntervalMs]);
10491
+ const taskBuckets = useMemo(
10492
+ () => groupMissionTasksByBucket(state.tasks),
10493
+ [state.tasks]
10494
+ );
10495
+ const runningWorkerCount = useMemo(
10496
+ () => state.workers.filter((w) => w.taskStatus === "running").length,
10497
+ [state.workers]
10498
+ );
10499
+ if (state.loading && !state.mission) {
10500
+ return /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center p-6", children: /* @__PURE__ */ jsx(Loader2, { className: "size-5 animate-spin text-foreground/70" }) });
10501
+ }
10502
+ if (state.error && !state.mission) {
10503
+ return /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-3", children: [
10504
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-destructive", children: [
10505
+ /* @__PURE__ */ jsx(AlertCircle, { className: "size-4" }),
10506
+ "Failed to load mission: ",
10507
+ state.error
10508
+ ] }),
10509
+ /* @__PURE__ */ jsxs(
10510
+ Button,
10511
+ {
10512
+ variant: "outline",
10513
+ size: "sm",
10514
+ onClick: () => {
10515
+ setState(INITIAL_STATE);
10516
+ void refresh();
10517
+ },
10518
+ children: [
10519
+ /* @__PURE__ */ jsx(RefreshCw, { className: "size-4" }),
10520
+ " Retry"
10521
+ ]
10522
+ }
10523
+ )
10524
+ ] });
10525
+ }
10526
+ const mission = state.mission;
10527
+ const doPause = async () => {
10528
+ setBusy(true);
10529
+ try {
10530
+ await api.pauseMission({ missionId });
10531
+ await refresh();
10532
+ } finally {
10533
+ setBusy(false);
10534
+ }
10535
+ };
10536
+ const doResume = async () => {
10537
+ setBusy(true);
10538
+ try {
10539
+ await api.resumeMission({ missionId });
10540
+ await refresh();
10541
+ } finally {
10542
+ setBusy(false);
10543
+ }
10544
+ };
10545
+ const doCancel = async () => {
10546
+ setBusy(true);
10547
+ try {
10548
+ await api.cancelMission({
10549
+ missionId,
10550
+ cascadeToWorkers: cancelCascade
10551
+ });
10552
+ await refresh();
10553
+ setCancelOpen(false);
10554
+ } finally {
10555
+ setBusy(false);
10556
+ }
10557
+ };
10558
+ const statusVariant = mission.status === "satisfied" ? "default" : mission.status === "failed" || mission.status === "cancelled" ? "destructive" : "secondary";
10559
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 p-6 max-w-6xl mx-auto", children: [
10560
+ /* @__PURE__ */ jsxs(Card, { children: [
10561
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
10562
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
10563
+ /* @__PURE__ */ jsx(CardTitle, { className: "wrap-break-word -uppercase text-sm", children: mission.description }),
10564
+ /* @__PURE__ */ jsxs(CardDescription, { className: "wrap-break-word text-foreground/70", children: [
10565
+ "Rubric: ",
10566
+ mission.rubric
10567
+ ] }),
10568
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 pt-2 text-xs text-foreground/70", children: [
10569
+ /* @__PURE__ */ jsx(Badge, { variant: statusVariant, className: "font-mono text-foreground", children: mission.status }),
10570
+ /* @__PURE__ */ jsxs("span", { className: "font-mono text-primary", children: [
10571
+ "Iteration ",
10572
+ mission.iteration,
10573
+ "/",
10574
+ mission.maxIterations
10575
+ ] }),
10576
+ mission.lastEvaluationResult ? /* @__PURE__ */ jsxs("span", { children: [
10577
+ "Last verdict:",
10578
+ " ",
10579
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-primary", children: mission.lastEvaluationResult })
10580
+ ] }) : null
10581
+ ] })
10582
+ ] }),
10583
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: ACTIVE_MISSION_STATUSES.has(mission.status) ? /* @__PURE__ */ jsxs(Fragment, { children: [
10584
+ mission.status === "active" ? /* @__PURE__ */ jsxs(
10585
+ Button,
10586
+ {
10587
+ size: "sm",
10588
+ variant: "outline",
10589
+ onClick: doPause,
10590
+ disabled: busy,
10591
+ children: [
10592
+ /* @__PURE__ */ jsx(Pause, { className: "size-4" }),
10593
+ " Pause"
10594
+ ]
10595
+ }
10596
+ ) : /* @__PURE__ */ jsxs(
10597
+ Button,
10598
+ {
10599
+ size: "sm",
10600
+ variant: "outline",
10601
+ onClick: doResume,
10602
+ disabled: busy,
10603
+ children: [
10604
+ /* @__PURE__ */ jsx(Play, { className: "size-4" }),
10605
+ " Resume"
10606
+ ]
10607
+ }
10608
+ ),
10609
+ /* @__PURE__ */ jsxs(
10610
+ Button,
10611
+ {
10612
+ size: "sm",
10613
+ variant: "destructive",
10614
+ onClick: () => {
10615
+ setCancelCascade(false);
10616
+ setCancelOpen(true);
10617
+ },
10618
+ disabled: busy,
10619
+ children: [
10620
+ /* @__PURE__ */ jsx(Ban, { className: "size-4" }),
10621
+ " Cancel"
10622
+ ]
10623
+ }
10624
+ )
10625
+ ] }) : onNavigateBack ? /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: onNavigateBack, children: "Back" }) : null })
10626
+ ] }) }),
10627
+ mission.lastEvaluationFeedback ? /* @__PURE__ */ jsxs(CardContent, { className: "space-y-2", children: [
10628
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-semibold uppercase tracking-widest text-foreground/70", children: "Evaluator feedback" }),
10629
+ /* @__PURE__ */ jsx("p", { className: "text-sm whitespace-pre-wrap wrap-break-word", children: mission.lastEvaluationFeedback }),
10630
+ mission.lastEvaluationExplanation ? /* @__PURE__ */ jsx("p", { className: "text-xs text-foreground/70 whitespace-pre-wrap wrap-break-word", children: mission.lastEvaluationExplanation }) : null
10631
+ ] }) : null
10632
+ ] }),
10633
+ /* @__PURE__ */ jsxs(Card, { children: [
10634
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, { children: [
10635
+ "Tasks (",
10636
+ state.tasks.length,
10637
+ ")"
10638
+ ] }) }),
10639
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
10640
+ ["in_flight", "blocked", "done", "failed_or_cancelled"].map((bucket) => {
10641
+ const rows = taskBuckets[bucket];
10642
+ if (rows.length === 0) return null;
10643
+ const heading = {
10644
+ in_flight: "In flight",
10645
+ blocked: "Blocked",
10646
+ done: "Done",
10647
+ failed_or_cancelled: "Failed / cancelled"
10648
+ }[bucket];
10649
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
10650
+ /* @__PURE__ */ jsxs("div", { className: "text-xs font-semibold uppercase tracking-widest text-foreground/70", children: [
10651
+ heading,
10652
+ " (",
10653
+ rows.length,
10654
+ ")"
10655
+ ] }),
10656
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1.5", children: rows.map((task) => /* @__PURE__ */ jsx(
10657
+ "li",
10658
+ {
10659
+ className: "flex items-start justify-between gap-3 rounded border border-border/40 px-3 py-2 text-sm",
10660
+ children: /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
10661
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
10662
+ task.agentDefName ? /* @__PURE__ */ jsx("span", { className: "text-xs text-foreground/70", children: task.agentDefName }) : null,
10663
+ task.attemptCount > 1 ? /* @__PURE__ */ jsxs("span", { className: "text-xs text-foreground/70", children: [
10664
+ "attempt ",
10665
+ task.attemptCount,
10666
+ "/",
10667
+ task.maxAttempts
10668
+ ] }) : null
10669
+ ] }),
10670
+ /* @__PURE__ */ jsx("div", { className: "text-sm wrap-break-word", children: task.goal }),
10671
+ task.parentIds.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "text-xs text-foreground/70", children: [
10672
+ "after:",
10673
+ " ",
10674
+ task.parentIds.map((p) => p.slice(0, 8)).join(", ")
10675
+ ] }) : null,
10676
+ task.result ? /* @__PURE__ */ jsx("pre", { className: "text-xs whitespace-pre-wrap bg-muted/30 rounded px-2 py-1 max-h-32 overflow-auto", children: task.result }) : null,
10677
+ task.resultMetadata ? /* @__PURE__ */ jsx("pre", { className: "text-xs whitespace-pre-wrap bg-muted/30 rounded px-2 py-1 max-h-32 overflow-auto", children: JSON.stringify(task.resultMetadata, null, 2) }) : null
10678
+ ] })
10679
+ },
10680
+ task.id
10681
+ )) })
10682
+ ] }, bucket);
10683
+ }),
10684
+ state.tasks.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-sm text-foreground/70", children: "No tasks spawned yet." }) : null
10685
+ ] })
10686
+ ] }),
10687
+ /* @__PURE__ */ jsxs(Card, { children: [
10688
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, { children: [
10689
+ "Live workers (",
10690
+ state.workers.length,
10691
+ ")"
10692
+ ] }) }),
10693
+ /* @__PURE__ */ jsx(CardContent, { children: state.workers.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-sm text-foreground/70", children: "No workers attached to this mission right now." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-1.5", children: state.workers.map((w) => {
10694
+ const onTranscriptClick = (e) => {
10695
+ if (onOpenTranscript) {
10696
+ e.preventDefault();
10697
+ onOpenTranscript(w.workerSessionId);
10698
+ }
10699
+ };
10700
+ return /* @__PURE__ */ jsxs(
10701
+ "li",
10702
+ {
10703
+ className: "flex items-start justify-between gap-3 rounded border border-border/40 px-3 py-2 text-sm",
10704
+ children: [
10705
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
10706
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
10707
+ /* @__PURE__ */ jsx(Badge, { className: "text-foreground/70", children: w.taskStatus }),
10708
+ w.agentDefName ? /* @__PURE__ */ jsx("span", { className: "text-xs text-foreground/70", children: w.agentDefName }) : null
10709
+ ] }),
10710
+ /* @__PURE__ */ jsx("div", { className: "text-sm wrap-break-word", children: deriveMissionWorkerActivityLabel(w) }),
10711
+ w.latestEventAt ? /* @__PURE__ */ jsx("div", { className: "text-xs text-foreground/70", children: new Date(w.latestEventAt).toLocaleTimeString() }) : null
10712
+ ] }),
10713
+ /* @__PURE__ */ jsx(
10714
+ "a",
10715
+ {
10716
+ href: w.transcriptUrl,
10717
+ target: "_blank",
10718
+ rel: "noreferrer",
10719
+ onClick: onTranscriptClick,
10720
+ className: "text-xs text-primary hover:underline self-start",
10721
+ children: "View session"
10722
+ }
10723
+ )
10724
+ ]
10725
+ },
10726
+ w.workerSessionId
10727
+ );
10728
+ }) }) })
10729
+ ] }),
10730
+ /* @__PURE__ */ jsx(Separator, {}),
10731
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center text-xs text-foreground/70", children: [
10732
+ /* @__PURE__ */ jsxs("span", { children: [
10733
+ "Auto-refresh every ",
10734
+ pollIntervalMs / 1e3,
10735
+ "s while mission is active."
10736
+ ] }),
10737
+ /* @__PURE__ */ jsxs(
10738
+ Button,
10739
+ {
10740
+ variant: "ghost",
10741
+ size: "sm",
10742
+ onClick: () => {
10743
+ void refresh();
10744
+ },
10745
+ disabled: busy,
10746
+ children: [
10747
+ /* @__PURE__ */ jsx(RefreshCw, { className: "size-4" }),
10748
+ " Refresh"
10749
+ ]
10750
+ }
10751
+ )
10752
+ ] }),
10753
+ /* @__PURE__ */ jsx(
10754
+ Dialog,
10755
+ {
10756
+ open: cancelOpen,
10757
+ onOpenChange: (open) => {
10758
+ if (!open && !busy) setCancelOpen(false);
10759
+ },
10760
+ children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
10761
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
10762
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Cancel mission?" }),
10763
+ /* @__PURE__ */ jsx(DialogDescription, { children: "This terminates the mission and clears its evaluator loop." })
10764
+ ] }),
10765
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
10766
+ /* @__PURE__ */ jsx(
10767
+ "input",
10768
+ {
10769
+ type: "checkbox",
10770
+ checked: cancelCascade,
10771
+ onChange: (e) => setCancelCascade(e.target.checked)
10772
+ }
10773
+ ),
10774
+ /* @__PURE__ */ jsxs("span", { children: [
10775
+ "Also cancel ",
10776
+ /* @__PURE__ */ jsx("strong", { children: runningWorkerCount }),
10777
+ " running worker",
10778
+ runningWorkerCount === 1 ? "" : "s",
10779
+ " (sends an interrupt to each worker session)."
10780
+ ] })
10781
+ ] }),
10782
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
10783
+ /* @__PURE__ */ jsx(
10784
+ Button,
10785
+ {
10786
+ variant: "outline",
10787
+ size: "sm",
10788
+ onClick: () => setCancelOpen(false),
10789
+ disabled: busy,
10790
+ children: "Keep mission"
10791
+ }
10792
+ ),
10793
+ /* @__PURE__ */ jsxs(
10794
+ Button,
10795
+ {
10796
+ variant: "destructive",
10797
+ size: "sm",
10798
+ onClick: doCancel,
10799
+ disabled: busy,
10800
+ children: [
10801
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(Ban, { className: "size-4" }),
10802
+ "Cancel mission"
10803
+ ]
10804
+ }
10805
+ )
10806
+ ] })
10807
+ ] })
10808
+ }
10809
+ )
10810
+ ] });
10811
+ }
10812
+ var DEFAULT_POLL_INTERVAL_MS2 = 3e4;
10813
+ var DEFAULT_STATUS = "active,paused";
10236
10814
  function fingerprint(items) {
10815
+ return items.map(
10816
+ (m) => `${m.id}:${m.status}:${m.iteration}:${m.lastEvaluationResult ?? ""}:${m.lastEvaluationAt ?? ""}:${m.pausedReason ?? ""}:${m.updatedAt}`
10817
+ ).join("|");
10818
+ }
10819
+ function sortMissions(items) {
10820
+ return [...items].sort((a, b) => {
10821
+ const rank = (s) => s === "active" ? 0 : s === "paused" ? 1 : 2;
10822
+ const diff = rank(a.status) - rank(b.status);
10823
+ if (diff !== 0) return diff;
10824
+ return (a.updatedAt ?? "") > (b.updatedAt ?? "") ? -1 : 1;
10825
+ });
10826
+ }
10827
+ function MissionRow({
10828
+ mission,
10829
+ onOpen,
10830
+ onPause,
10831
+ onResume,
10832
+ onCancel,
10833
+ busy,
10834
+ canMutate
10835
+ }) {
10836
+ const isActive = mission.status === "active";
10837
+ const isPaused = mission.status === "paused";
10838
+ const description = mission.description.trim() || "(no description)";
10839
+ const verdict = mission.lastEvaluationResult;
10840
+ const verdictAt = mission.lastEvaluationAt ? formatDistanceToNow(new Date(mission.lastEvaluationAt), {
10841
+ addSuffix: true
10842
+ }) : null;
10843
+ const meta = [
10844
+ `Iter ${mission.iteration}/${mission.maxIterations}`,
10845
+ verdict ? `Verdict: ${verdict}${verdictAt ? ` (${verdictAt})` : ""}` : null,
10846
+ isPaused && mission.pausedReason ? `Paused: ${mission.pausedReason}` : null
10847
+ ].filter(Boolean);
10848
+ const stopActionClick = (e) => {
10849
+ e.stopPropagation();
10850
+ };
10851
+ const openMission = () => onOpen(mission.id);
10852
+ const handleRowKeyDown = (e) => {
10853
+ if (e.key !== "Enter" && e.key !== " ") return;
10854
+ e.preventDefault();
10855
+ openMission();
10856
+ };
10857
+ return /* @__PURE__ */ jsxs(
10858
+ "div",
10859
+ {
10860
+ className: cn(
10861
+ "group flex min-w-0 items-start gap-2 border-l-2 border-l-transparent px-3 py-2 text-sm transition-colors cursor-pointer hover:border-l-primary hover:bg-input"
10862
+ ),
10863
+ onClick: openMission,
10864
+ onKeyDown: handleRowKeyDown,
10865
+ role: "button",
10866
+ tabIndex: 0,
10867
+ "aria-label": `Open mission ${description}`,
10868
+ title: "Open mission dashboard",
10869
+ children: [
10870
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex size-5 shrink-0 items-center justify-center text-faint", children: /* @__PURE__ */ jsx(TargetIcon, { className: "size-3.5" }) }),
10871
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx("div", { className: "flex min-w-0 items-start gap-2", children: /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
10872
+ /* @__PURE__ */ jsx("div", { className: "truncate text-foreground", children: description }),
10873
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex min-w-0 items-center gap-2", children: /* @__PURE__ */ jsx(
10874
+ Badge,
10875
+ {
10876
+ variant: isActive ? "default" : "secondary",
10877
+ className: "shrink-0",
10878
+ children: mission.status
10879
+ }
10880
+ ) }),
10881
+ meta.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-0.5 space-y-0.5 text-xs text-faint", children: meta.map((line) => /* @__PURE__ */ jsx("div", { className: "truncate", children: line }, line)) })
10882
+ ] }) }) }),
10883
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
10884
+ isActive && canMutate.pause && /* @__PURE__ */ jsx(
10885
+ "button",
10886
+ {
10887
+ type: "button",
10888
+ className: "rounded p-1 text-faint opacity-70 transition-all hover:bg-line hover:text-foreground disabled:pointer-events-none disabled:opacity-40 group-hover:opacity-100",
10889
+ onClick: (e) => {
10890
+ stopActionClick(e);
10891
+ onPause(mission.id);
10892
+ },
10893
+ "aria-label": "Pause mission",
10894
+ title: "Pause mission",
10895
+ disabled: busy,
10896
+ children: /* @__PURE__ */ jsx(Pause, { className: "size-3.5" })
10897
+ }
10898
+ ),
10899
+ isPaused && canMutate.resume && /* @__PURE__ */ jsx(
10900
+ "button",
10901
+ {
10902
+ type: "button",
10903
+ className: "rounded p-1 text-faint opacity-70 transition-all hover:bg-line hover:text-foreground disabled:pointer-events-none disabled:opacity-40 group-hover:opacity-100",
10904
+ onClick: (e) => {
10905
+ stopActionClick(e);
10906
+ onResume(mission.id);
10907
+ },
10908
+ "aria-label": "Resume mission",
10909
+ title: "Resume mission",
10910
+ disabled: busy,
10911
+ children: /* @__PURE__ */ jsx(Play, { className: "size-3.5" })
10912
+ }
10913
+ ),
10914
+ (isActive || isPaused) && canMutate.cancel && /* @__PURE__ */ jsx(
10915
+ "button",
10916
+ {
10917
+ type: "button",
10918
+ className: "rounded p-1 text-faint opacity-70 transition-all hover:bg-destructive/10 hover:text-destructive disabled:pointer-events-none disabled:opacity-40 group-hover:opacity-100",
10919
+ onClick: (e) => {
10920
+ stopActionClick(e);
10921
+ onCancel(mission.id);
10922
+ },
10923
+ "aria-label": "Cancel mission",
10924
+ title: "Cancel mission",
10925
+ disabled: busy,
10926
+ children: /* @__PURE__ */ jsx(Ban, { className: "size-3.5" })
10927
+ }
10928
+ )
10929
+ ] })
10930
+ ]
10931
+ }
10932
+ );
10933
+ }
10934
+ function MissionsPanel({
10935
+ adapter,
10936
+ agentId,
10937
+ title = "Missions",
10938
+ status = DEFAULT_STATUS,
10939
+ hideHeader = false,
10940
+ pollIntervalMs = DEFAULT_POLL_INTERVAL_MS2,
10941
+ onMissionSelect,
10942
+ onMissionPause,
10943
+ onMissionResume,
10944
+ onMissionCancel
10945
+ }) {
10946
+ const [items, setItems] = useState([]);
10947
+ const [error, setError] = useState(null);
10948
+ const [hasEverLoaded, setHasEverLoaded] = useState(false);
10949
+ const [busyMissionId, setBusyMissionId] = useState(null);
10950
+ const [collapsed, setCollapsed] = useState(false);
10951
+ const mounted = useRef(true);
10952
+ const requestId = useRef(0);
10953
+ const lastFingerprint = useRef("");
10954
+ const refetch = useCallback(
10955
+ async (opts) => {
10956
+ if (!adapter.listMissions) {
10957
+ setItems([]);
10958
+ setHasEverLoaded(true);
10959
+ return;
10960
+ }
10961
+ const currentRequestId = ++requestId.current;
10962
+ try {
10963
+ const list = await adapter.listMissions({ agentId, status });
10964
+ if (!mounted.current || currentRequestId !== requestId.current) return;
10965
+ const next = sortMissions(list.missions);
10966
+ const fp = fingerprint(next);
10967
+ if (fp !== lastFingerprint.current) {
10968
+ lastFingerprint.current = fp;
10969
+ setItems(next);
10970
+ }
10971
+ setError(null);
10972
+ setHasEverLoaded(true);
10973
+ } catch (e) {
10974
+ if (!mounted.current || currentRequestId !== requestId.current) return;
10975
+ if (!opts?.silent) {
10976
+ setError(
10977
+ e instanceof Error ? e.message : "Failed to load missions"
10978
+ );
10979
+ setHasEverLoaded(true);
10980
+ }
10981
+ }
10982
+ },
10983
+ [adapter, agentId, status]
10984
+ );
10985
+ useEffect(() => {
10986
+ mounted.current = true;
10987
+ return () => {
10988
+ mounted.current = false;
10989
+ };
10990
+ }, []);
10991
+ useEffect(() => {
10992
+ setError(null);
10993
+ lastFingerprint.current = "";
10994
+ void refetch();
10995
+ }, [refetch]);
10996
+ useEffect(() => {
10997
+ if (!adapter.listMissions || pollIntervalMs <= 0) return;
10998
+ const id = setInterval(() => {
10999
+ void refetch({ silent: true });
11000
+ }, pollIntervalMs);
11001
+ return () => clearInterval(id);
11002
+ }, [adapter.listMissions, pollIntervalMs, refetch]);
11003
+ const activeCount = useMemo(
11004
+ () => items.filter((m) => m.status === "active").length,
11005
+ [items]
11006
+ );
11007
+ const handleOpen = useCallback(
11008
+ (missionId) => {
11009
+ onMissionSelect?.(missionId);
11010
+ },
11011
+ [onMissionSelect]
11012
+ );
11013
+ const handlePause = useCallback(
11014
+ async (missionId) => {
11015
+ if (!adapter.pauseMission || busyMissionId) return;
11016
+ setBusyMissionId(missionId);
11017
+ try {
11018
+ await adapter.pauseMission({ missionId });
11019
+ onMissionPause?.(missionId);
11020
+ await refetch({ silent: true });
11021
+ } catch (e) {
11022
+ setError(e instanceof Error ? e.message : "Failed to pause mission");
11023
+ } finally {
11024
+ if (mounted.current) setBusyMissionId(null);
11025
+ }
11026
+ },
11027
+ [adapter, busyMissionId, onMissionPause, refetch]
11028
+ );
11029
+ const handleResume = useCallback(
11030
+ async (missionId) => {
11031
+ if (!adapter.resumeMission || busyMissionId) return;
11032
+ setBusyMissionId(missionId);
11033
+ try {
11034
+ await adapter.resumeMission({ missionId });
11035
+ onMissionResume?.(missionId);
11036
+ await refetch({ silent: true });
11037
+ } catch (e) {
11038
+ setError(e instanceof Error ? e.message : "Failed to resume mission");
11039
+ } finally {
11040
+ if (mounted.current) setBusyMissionId(null);
11041
+ }
11042
+ },
11043
+ [adapter, busyMissionId, onMissionResume, refetch]
11044
+ );
11045
+ const handleCancel = useCallback(
11046
+ async (missionId) => {
11047
+ if (!adapter.cancelMission || busyMissionId) return;
11048
+ setBusyMissionId(missionId);
11049
+ try {
11050
+ await adapter.cancelMission({ missionId });
11051
+ setItems((current) => {
11052
+ const next = current.filter((m) => m.id !== missionId);
11053
+ lastFingerprint.current = fingerprint(next);
11054
+ return next;
11055
+ });
11056
+ onMissionCancel?.(missionId);
11057
+ await refetch({ silent: true });
11058
+ } catch (e) {
11059
+ setError(e instanceof Error ? e.message : "Failed to cancel mission");
11060
+ } finally {
11061
+ if (mounted.current) setBusyMissionId(null);
11062
+ }
11063
+ },
11064
+ [adapter, busyMissionId, onMissionCancel, refetch]
11065
+ );
11066
+ if (!hasEverLoaded) return null;
11067
+ if (!adapter.listMissions) return null;
11068
+ const showBody = hideHeader || !collapsed;
11069
+ const canMutate = {
11070
+ pause: Boolean(adapter.pauseMission),
11071
+ resume: Boolean(adapter.resumeMission),
11072
+ cancel: Boolean(adapter.cancelMission)
11073
+ };
11074
+ return /* @__PURE__ */ jsxs("section", { className: "min-w-0 border-t border-line", children: [
11075
+ !hideHeader && /* @__PURE__ */ jsxs(
11076
+ "button",
11077
+ {
11078
+ type: "button",
11079
+ className: "flex w-full items-center gap-1.5 px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-foreground transition-colors hover:bg-input",
11080
+ onClick: () => setCollapsed((c) => !c),
11081
+ "aria-expanded": !collapsed,
11082
+ children: [
11083
+ /* @__PURE__ */ jsx(TargetIcon, { className: "size-3.5" }),
11084
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: title }),
11085
+ activeCount > 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", className: "ml-auto", children: activeCount }),
11086
+ collapsed ? /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5 shrink-0 text-faint" }) : /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3.5 shrink-0 text-faint" })
11087
+ ]
11088
+ }
11089
+ ),
11090
+ showBody && error && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-destructive", children: error }),
11091
+ showBody && !error && items.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-faint", children: "No missions" }),
11092
+ showBody && !error && items.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("pb-2", hideHeader && "pt-1"), children: items.map((mission) => /* @__PURE__ */ jsx(
11093
+ MissionRow,
11094
+ {
11095
+ mission,
11096
+ busy: busyMissionId === mission.id,
11097
+ canMutate,
11098
+ onOpen: handleOpen,
11099
+ onPause: handlePause,
11100
+ onResume: handleResume,
11101
+ onCancel: handleCancel
11102
+ },
11103
+ mission.id
11104
+ )) })
11105
+ ] });
11106
+ }
11107
+ var DEFAULT_LIMIT2 = 50;
11108
+ var DEFAULT_POLL_INTERVAL_MS3 = 3e4;
11109
+ function fingerprint2(items) {
10237
11110
  return items.map(
10238
11111
  (item) => `${item.id}:${item.status}:${item.kind ?? ""}:${item.name ?? ""}:${item.prompt}:${item.scheduleDisplay}:${item.runCount}:${item.nextRunAt ?? ""}:${item.lastRunAt ?? ""}:${item.lastSessionId ?? ""}:${item.lastError ?? ""}:${item.updatedAt}`
10239
11112
  ).join("|");
@@ -10383,7 +11256,7 @@ function ScheduledWorkPanel({
10383
11256
  limit = DEFAULT_LIMIT2,
10384
11257
  status = "active",
10385
11258
  hideHeader = false,
10386
- pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
11259
+ pollIntervalMs = DEFAULT_POLL_INTERVAL_MS3,
10387
11260
  onSessionSelect,
10388
11261
  onScheduleCancel,
10389
11262
  onScheduleRunNow
@@ -10412,7 +11285,7 @@ function ScheduledWorkPanel({
10412
11285
  });
10413
11286
  if (!mounted.current || currentRequestId !== requestId.current) return;
10414
11287
  const nextItems = sortScheduledWork(list.items);
10415
- const fp = fingerprint(nextItems);
11288
+ const fp = fingerprint2(nextItems);
10416
11289
  if (fp !== lastFingerprint.current) {
10417
11290
  lastFingerprint.current = fp;
10418
11291
  setItems(nextItems);
@@ -10483,7 +11356,7 @@ function ScheduledWorkPanel({
10483
11356
  await adapter.cancelScheduledWork({ scheduleId });
10484
11357
  setItems((current) => {
10485
11358
  const next = current.filter((item) => item.id !== scheduleId);
10486
- lastFingerprint.current = fingerprint(next);
11359
+ lastFingerprint.current = fingerprint2(next);
10487
11360
  return next;
10488
11361
  });
10489
11362
  onScheduleCancel?.(scheduleId);
@@ -10902,6 +11775,6 @@ function SessionTreePanel({
10902
11775
  ] });
10903
11776
  }
10904
11777
 
10905
- export { AgentChat, AgentChatAdapterProvider, InboxPanel, MessageResponse, ScheduledWorkPanel, SessionTreePanel, useAgentChatAdapterContext, useAgentChatRuntime, useInboxUnreadCount };
11778
+ export { ACTIVE_MISSION_STATUSES, AgentChat, AgentChatAdapterProvider, InboxPanel, MessageResponse, MissionDashboard, MissionsPanel, ScheduledWorkPanel, SessionTreePanel, deriveMissionWorkerActivityLabel, groupMissionTasksByBucket, isTerminalMissionStatus, missionTaskBucket, useAgentChatAdapterContext, useAgentChatRuntime, useInboxUnreadCount };
10906
11779
  //# sourceMappingURL=index.js.map
10907
11780
  //# sourceMappingURL=index.js.map