@invergent/agent-chat-react 1.5.9 → 1.5.10

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
@@ -3,7 +3,7 @@ import { createContext, memo, useRef, useState, useEffect, useCallback, useMemo,
3
3
  import { cva } from 'class-variance-authority';
4
4
  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
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, UsersIcon, MessageSquareIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
6
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
7
7
  import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
8
8
  import { cjk } from '@streamdown/cjk';
9
9
  import { code } from '@streamdown/code';
@@ -3304,13 +3304,16 @@ function ClarifyLocked({
3304
3304
  }) })
3305
3305
  ] });
3306
3306
  }
3307
- function ArtifactToolBlock({ tc }) {
3308
- const args = parseArgs(tc.args) ?? {};
3307
+ function ArtifactToolBlock({
3308
+ tc,
3309
+ resolvedName
3310
+ }) {
3309
3311
  const status = effectiveStatus(tc);
3310
3312
  const label = status === "running" ? "Creating artifact\u2026" : status === "error" ? "Creating artifact\u2026" : "Created";
3313
+ const name = resolvedName ?? parseArgs(tc.args)?.name;
3311
3314
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-sm ", children: [
3312
3315
  /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: label }),
3313
- args.name && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate", children: args.name })
3316
+ name && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate", children: name })
3314
3317
  ] });
3315
3318
  }
3316
3319
  function firstLine(s) {
@@ -3636,7 +3639,11 @@ function DefaultToolBlock({ tc }) {
3636
3639
  ] })
3637
3640
  ] });
3638
3641
  }
3639
- function ToolCallBlock({ tc, onFileSelect }) {
3642
+ function ToolCallBlock({
3643
+ tc,
3644
+ resolvedArtifactName,
3645
+ onFileSelect
3646
+ }) {
3640
3647
  switch (tc.toolName) {
3641
3648
  case "terminal":
3642
3649
  return /* @__PURE__ */ jsx(TerminalToolBlock, { tc });
@@ -3673,7 +3680,7 @@ function ToolCallBlock({ tc, onFileSelect }) {
3673
3680
  case "clarify":
3674
3681
  return /* @__PURE__ */ jsx(ClarifyToolBlock, { tc });
3675
3682
  case "create_artifact":
3676
- return /* @__PURE__ */ jsx(ArtifactToolBlock, { tc });
3683
+ return /* @__PURE__ */ jsx(ArtifactToolBlock, { tc, resolvedName: resolvedArtifactName });
3677
3684
  case "delegate_task":
3678
3685
  return /* @__PURE__ */ jsx(DelegateToolBlock, { tc });
3679
3686
  case "memory":
@@ -5055,6 +5062,7 @@ function ChatComposerInner({
5055
5062
  onStop,
5056
5063
  isRunning,
5057
5064
  disabled = false,
5065
+ disabledReason,
5058
5066
  tokenUsage
5059
5067
  }) {
5060
5068
  const { adapter } = useAgentChatAdapterContext();
@@ -5083,7 +5091,10 @@ function ChatComposerInner({
5083
5091
  const slashCommands = useMemo(() => {
5084
5092
  const builtin = [
5085
5093
  { value: "/clear", label: "/clear", description: "Clear conversation" },
5086
- { value: "/compress", label: "/compress", description: "Compress context" }
5094
+ { value: "/compress", label: "/compress", description: "Compress context" },
5095
+ { value: "/loop", label: "/loop", description: "Schedule recurring prompt" },
5096
+ { value: "/loop list", label: "/loop list", description: "List active loops" },
5097
+ { value: "/loop cancel", label: "/loop cancel", description: "Cancel a loop by ID" }
5087
5098
  ];
5088
5099
  return [...adapterCommands, ...builtin];
5089
5100
  }, [adapterCommands]);
@@ -5162,7 +5173,7 @@ function ChatComposerInner({
5162
5173
  /* @__PURE__ */ jsx(PromptInputBody, { children: /* @__PURE__ */ jsx(
5163
5174
  PromptInputTextarea,
5164
5175
  {
5165
- placeholder: disabled ? "Session disabled" : "Send a message...",
5176
+ placeholder: disabled ? disabledReason ?? "Session disabled" : "Send a message...",
5166
5177
  disabled,
5167
5178
  onKeyDown: handleKeyDown
5168
5179
  }
@@ -5807,7 +5818,7 @@ var ErrorMessage = memo(function ErrorMessage2({
5807
5818
  }
5808
5819
  );
5809
5820
  });
5810
- function messageToEntries(msg, isLast) {
5821
+ function messageToEntries(msg, isLast, artifactFallbacks) {
5811
5822
  if (msg.role === "system") {
5812
5823
  if (msg.systemKind === "skill_invoked") {
5813
5824
  return [{
@@ -5849,6 +5860,18 @@ function messageToEntries(msg, isLast) {
5849
5860
  }
5850
5861
  if (hasToolCalls) {
5851
5862
  for (const tc of msg.toolCalls) {
5863
+ if (tc.toolName === "create_artifact") {
5864
+ const resolvedName = parseArgs(tc.args)?.name ?? artifactFallbacks[tc.id];
5865
+ const status = effectiveStatus(tc);
5866
+ if (!resolvedName && status !== "error") continue;
5867
+ entries.push({
5868
+ kind: "tool",
5869
+ key: tc.id,
5870
+ tc,
5871
+ resolvedArtifactName: resolvedName
5872
+ });
5873
+ continue;
5874
+ }
5852
5875
  entries.push({ kind: "tool", key: tc.id, tc });
5853
5876
  }
5854
5877
  }
@@ -6007,7 +6030,14 @@ function TimelineEntryItem({
6007
6030
  )
6008
6031
  ] }),
6009
6032
  /* @__PURE__ */ jsx(TimelineContent, { children: entry.tc.cancelled ? /* @__PURE__ */ jsx(CancelledToolRow, { tc: entry.tc }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
6010
- /* @__PURE__ */ jsx(ToolCallBlock, { tc: entry.tc, onFileSelect }),
6033
+ /* @__PURE__ */ jsx(
6034
+ ToolCallBlock,
6035
+ {
6036
+ tc: entry.tc,
6037
+ resolvedArtifactName: entry.resolvedArtifactName,
6038
+ onFileSelect
6039
+ }
6040
+ ),
6011
6041
  failureSummary ? /* @__PURE__ */ jsx("div", { className: "max-w-full truncate text-xs text-destructive", title: failureSummary, children: failureSummary }) : null
6012
6042
  ] }) })
6013
6043
  ] });
@@ -6069,13 +6099,14 @@ function AssistantGroup({
6069
6099
  totalMessages,
6070
6100
  isRunning,
6071
6101
  sessionId,
6102
+ artifactFallbacks,
6072
6103
  onFileSelect,
6073
6104
  onRetry
6074
6105
  }) {
6075
6106
  const entries = [];
6076
6107
  for (let i = 0; i < messages.length; i++) {
6077
6108
  const isLast = i === messages.length - 1 && lastGlobalIndex === totalMessages - 1;
6078
- entries.push(...messageToEntries(messages[i], isLast));
6109
+ entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
6079
6110
  }
6080
6111
  const isTailGroup = lastGlobalIndex === totalMessages - 1;
6081
6112
  const lastEntry = entries[entries.length - 1];
@@ -6142,11 +6173,34 @@ function ChatThread({
6142
6173
  onStop,
6143
6174
  onFileSelect,
6144
6175
  disabled = false,
6176
+ disabledReason,
6145
6177
  tokenUsage,
6146
6178
  retryIndicator,
6147
6179
  onRetry
6148
6180
  }) {
6149
6181
  const groups = useMemo(() => groupMessages(messages), [messages]);
6182
+ const artifactFallbacks = useMemo(() => {
6183
+ const fallbacks = {};
6184
+ const createArtifactToolCallIds = [];
6185
+ let artifactIdx = 0;
6186
+ for (const msg of messages) {
6187
+ if (msg.role === "assistant" && msg.toolCalls) {
6188
+ for (const tc of msg.toolCalls) {
6189
+ if (tc.toolName === "create_artifact" && !tc.cancelled && tc.status !== "error") {
6190
+ createArtifactToolCallIds.push(tc.id);
6191
+ }
6192
+ }
6193
+ } else if (msg.role === "system" && msg.systemKind === "artifact") {
6194
+ const tcId = createArtifactToolCallIds[artifactIdx];
6195
+ if (tcId) {
6196
+ const { name } = unpackArtifactMeta(msg.systemMeta, msg.content);
6197
+ if (name) fallbacks[tcId] = name;
6198
+ }
6199
+ artifactIdx += 1;
6200
+ }
6201
+ }
6202
+ return fallbacks;
6203
+ }, [messages]);
6150
6204
  const activeFailureId = useMemo(() => {
6151
6205
  if (messages.length === 0) return null;
6152
6206
  const tail = messages[messages.length - 1];
@@ -6211,6 +6265,7 @@ function ChatThread({
6211
6265
  totalMessages: messages.length,
6212
6266
  isRunning,
6213
6267
  sessionId,
6268
+ artifactFallbacks,
6214
6269
  onFileSelect,
6215
6270
  onRetry: groupRetry
6216
6271
  },
@@ -6223,13 +6278,21 @@ function ChatThread({
6223
6278
  ] }),
6224
6279
  /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-4xl px-6 pb-5 pt-3", children: [
6225
6280
  retryIndicator && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(RetryBanner, { indicator: retryIndicator }) }),
6226
- /* @__PURE__ */ jsx(
6281
+ disabled && disabledReason ? /* @__PURE__ */ jsx(
6282
+ "div",
6283
+ {
6284
+ className: "rounded border border-line bg-muted/40 px-3 py-2 text-sm text-muted-foreground",
6285
+ role: "status",
6286
+ children: disabledReason
6287
+ }
6288
+ ) : /* @__PURE__ */ jsx(
6227
6289
  ChatComposer,
6228
6290
  {
6229
6291
  onSend,
6230
6292
  onStop,
6231
6293
  isRunning,
6232
6294
  disabled,
6295
+ disabledReason,
6233
6296
  tokenUsage
6234
6297
  }
6235
6298
  )
@@ -7058,7 +7121,7 @@ function WorkspacePanel({
7058
7121
  );
7059
7122
  const handleDelete = useCallback(
7060
7123
  async (path) => {
7061
- if (!sessionId) return;
7124
+ if (disabled || !sessionId) return;
7062
7125
  try {
7063
7126
  await adapter.deleteWorkspaceFile({ sessionId, path });
7064
7127
  if (selectedPath === path) {
@@ -7074,7 +7137,7 @@ function WorkspacePanel({
7074
7137
  setDeleteTarget(null);
7075
7138
  }
7076
7139
  },
7077
- [adapter, fetchTree, onSelectedPathChange, selectedPath, sessionId]
7140
+ [adapter, disabled, fetchTree, onSelectedPathChange, selectedPath, sessionId]
7078
7141
  );
7079
7142
  const onResizeStart = useCallback(
7080
7143
  (event) => {
@@ -7157,7 +7220,7 @@ function WorkspacePanel({
7157
7220
  /* @__PURE__ */ jsx(FolderOpenIcon, { className: "size-4 shrink-0 text-amber-500" }),
7158
7221
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
7159
7222
  /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium text-foreground", children: rootName }),
7160
- /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-faint", children: "Workspace" })
7223
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-faint", children: disabled ? "Workspace - Read-only" : "Workspace" })
7161
7224
  ] }),
7162
7225
  /* @__PURE__ */ jsxs(Tooltip, { children: [
7163
7226
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
@@ -7268,7 +7331,7 @@ function WorkspacePanel({
7268
7331
  sessionId,
7269
7332
  path: file.path
7270
7333
  }) : null,
7271
- onDelete: file ? () => setDeleteTarget(file.path) : null,
7334
+ onDelete: file && !disabled ? () => setDeleteTarget(file.path) : null,
7272
7335
  onClose: () => {
7273
7336
  onSelectedPathChange(null);
7274
7337
  setFile(null);
@@ -7332,6 +7395,13 @@ function ConfirmDialog({
7332
7395
  );
7333
7396
  }
7334
7397
 
7398
+ // src/lib/sessions.ts
7399
+ function isScheduledRunSession(session) {
7400
+ return Boolean(
7401
+ session?.channel === "scheduled" || session?.config?.scheduled_session_id
7402
+ );
7403
+ }
7404
+
7335
7405
  // src/runtime/events.ts
7336
7406
  var WORKSPACE_MUTATING_TOOLS = /* @__PURE__ */ new Set([
7337
7407
  "terminal",
@@ -7944,6 +8014,7 @@ function useAgentChatRuntime({
7944
8014
  const [state, setState] = useState(
7945
8015
  () => createInitialAgentChatState({ isLoadingHistory: Boolean(sessionId) })
7946
8016
  );
8017
+ const [session, setSession] = useState(null);
7947
8018
  const stateRef = useRef(state);
7948
8019
  const streamRef = useRef(null);
7949
8020
  const reconnectTimerRef = useRef(null);
@@ -7968,6 +8039,7 @@ function useAgentChatRuntime({
7968
8039
  clearReconnectTimer();
7969
8040
  closeStream();
7970
8041
  if (!sessionId) {
8042
+ setSession(null);
7971
8043
  setState(createInitialAgentChatState());
7972
8044
  return;
7973
8045
  }
@@ -8015,17 +8087,19 @@ function useAgentChatRuntime({
8015
8087
  isLoadingHistory: true
8016
8088
  });
8017
8089
  stateRef.current = initialState;
8090
+ setSession(null);
8018
8091
  setState(initialState);
8019
8092
  connect(0);
8020
- adapter.getSession({ sessionId }).then((session) => {
8093
+ adapter.getSession({ sessionId }).then((loadedSession) => {
8021
8094
  if (cancelled) return;
8022
- if (session.messageCount === 0) {
8095
+ setSession(loadedSession);
8096
+ if (loadedSession.messageCount === 0) {
8023
8097
  setState((prev) => ({
8024
8098
  ...prev,
8025
8099
  isLoadingHistory: false
8026
8100
  }));
8027
8101
  }
8028
- if (isTerminalStatus(session.status)) {
8102
+ if (isTerminalStatus(loadedSession.status)) {
8029
8103
  setState((prev) => ({
8030
8104
  ...prev,
8031
8105
  terminal: true,
@@ -8114,9 +8188,9 @@ function useAgentChatRuntime({
8114
8188
  markSending(content, images);
8115
8189
  if (!sessionId) {
8116
8190
  try {
8117
- const session = await adapter.createSession({ agentId });
8118
- onSessionChange?.(session.id);
8119
- await adapter.sendMessage({ sessionId: session.id, content, images });
8191
+ const session2 = await adapter.createSession({ agentId });
8192
+ onSessionChange?.(session2.id);
8193
+ await adapter.sendMessage({ sessionId: session2.id, content, images });
8120
8194
  } catch (error) {
8121
8195
  markSendError(error instanceof Error ? error.message : "send failed");
8122
8196
  throw error;
@@ -8157,6 +8231,7 @@ function useAgentChatRuntime({
8157
8231
  }
8158
8232
  }, [adapter, sessionId]);
8159
8233
  return {
8234
+ session,
8160
8235
  messages: state.messages,
8161
8236
  isRunning: state.isRunning,
8162
8237
  isLoadingHistory: state.isLoadingHistory,
@@ -8204,6 +8279,9 @@ function AgentChat({
8204
8279
  sessionId,
8205
8280
  onSessionChange
8206
8281
  });
8282
+ const isReadOnlySession = isScheduledRunSession(runtime.session);
8283
+ const effectiveDisabled = disabled || isReadOnlySession;
8284
+ const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
8207
8285
  useEffect(() => {
8208
8286
  onMessagesChange?.(runtime.messages);
8209
8287
  }, [onMessagesChange, runtime.messages]);
@@ -8234,7 +8312,8 @@ function AgentChat({
8234
8312
  onStop: () => void runtime.stop(),
8235
8313
  onRetry: runtime.retry,
8236
8314
  onFileSelect: handleFileSelect,
8237
- disabled,
8315
+ disabled: effectiveDisabled,
8316
+ disabledReason,
8238
8317
  tokenUsage: runtime.tokenUsage,
8239
8318
  retryIndicator: runtime.retryIndicator
8240
8319
  }
@@ -8249,13 +8328,313 @@ function AgentChat({
8249
8328
  collapsed: workspaceCollapsed,
8250
8329
  onCollapsedChange: setWorkspaceCollapsed,
8251
8330
  refreshSignal: runtime.workspaceRefreshKey,
8252
- disabled
8331
+ disabled: effectiveDisabled
8253
8332
  }
8254
8333
  )
8255
8334
  ] }) })
8256
8335
  }
8257
8336
  );
8258
8337
  }
8338
+ var DEFAULT_LIMIT = 50;
8339
+ var DEFAULT_POLL_INTERVAL_MS = 3e4;
8340
+ function fingerprint(items) {
8341
+ return items.map(
8342
+ (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}`
8343
+ ).join("|");
8344
+ }
8345
+ function formatKind(value) {
8346
+ if (value === "dynamic_loop") return "Dynamic loop";
8347
+ if (value === "cron") return "Cron";
8348
+ if (value === "one_shot") return "One-shot";
8349
+ if (value === "scheduled") return "Scheduled";
8350
+ if (!value) return "Scheduled";
8351
+ return value.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
8352
+ }
8353
+ function formatRelative(value, label) {
8354
+ if (!value) return null;
8355
+ const date = new Date(value);
8356
+ if (Number.isNaN(date.getTime())) return null;
8357
+ return `${label} ${formatDistanceToNow(date, { addSuffix: true })}`;
8358
+ }
8359
+ function formatRuns(count) {
8360
+ return `${count} ${count === 1 ? "run" : "runs"}`;
8361
+ }
8362
+ function scheduleTitle(item) {
8363
+ const title = item.name?.trim();
8364
+ if (title) return title;
8365
+ return item.prompt;
8366
+ }
8367
+ function canRunScheduleNow(item) {
8368
+ return item.repeatLimit === 1;
8369
+ }
8370
+ function scheduleDisplay(item) {
8371
+ if (item.kind === "dynamic_loop") return null;
8372
+ return item.scheduleDisplay;
8373
+ }
8374
+ function sortScheduledWork(items) {
8375
+ return [...items].sort((a, b) => {
8376
+ if (a.status === "active" && b.status !== "active") return -1;
8377
+ if (a.status !== "active" && b.status === "active") return 1;
8378
+ const aNext = a.nextRunAt ?? "";
8379
+ const bNext = b.nextRunAt ?? "";
8380
+ if (aNext && bNext && aNext !== bNext) return aNext < bNext ? -1 : 1;
8381
+ if (aNext && !bNext) return -1;
8382
+ if (!aNext && bNext) return 1;
8383
+ return (a.updatedAt ?? "") > (b.updatedAt ?? "") ? -1 : 1;
8384
+ });
8385
+ }
8386
+ function ScheduledWorkRow({
8387
+ item,
8388
+ canRunNow,
8389
+ canCancel,
8390
+ actionScheduleId,
8391
+ onOpenLastRun,
8392
+ onRunNow,
8393
+ onCancel
8394
+ }) {
8395
+ const isActive = item.status === "active";
8396
+ const disabled = actionScheduleId === item.id;
8397
+ const title = scheduleTitle(item);
8398
+ const statusText = item.status === "active" ? null : item.status;
8399
+ const showRunNow = canRunNow && isActive && canRunScheduleNow(item);
8400
+ const lastSessionId = item.lastSessionId ?? null;
8401
+ const meta = [
8402
+ scheduleDisplay(item),
8403
+ formatRelative(item.nextRunAt, "Next"),
8404
+ formatRelative(item.lastRunAt, "Last"),
8405
+ formatRuns(item.runCount),
8406
+ item.expiresAt ? formatRelative(item.expiresAt, "Expires") : null
8407
+ ].filter(Boolean);
8408
+ const openLastRun = () => {
8409
+ if (lastSessionId) onOpenLastRun(lastSessionId);
8410
+ };
8411
+ const handleRowKeyDown = (e) => {
8412
+ if (!lastSessionId) return;
8413
+ if (e.key !== "Enter" && e.key !== " ") return;
8414
+ e.preventDefault();
8415
+ openLastRun();
8416
+ };
8417
+ const stopActionClick = (e) => {
8418
+ e.stopPropagation();
8419
+ };
8420
+ return /* @__PURE__ */ jsxs(
8421
+ "div",
8422
+ {
8423
+ className: cn(
8424
+ "group flex min-w-0 items-start gap-2 border-l-2 border-l-transparent px-3 py-2 text-sm transition-colors hover:border-l-primary hover:bg-input",
8425
+ lastSessionId && "cursor-pointer"
8426
+ ),
8427
+ onClick: openLastRun,
8428
+ onKeyDown: handleRowKeyDown,
8429
+ role: lastSessionId ? "button" : void 0,
8430
+ tabIndex: lastSessionId ? 0 : void 0,
8431
+ "aria-label": lastSessionId ? `Open last run for ${title}` : void 0,
8432
+ title: lastSessionId ? "Open last run" : void 0,
8433
+ children: [
8434
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex size-5 shrink-0 items-center justify-center text-faint", children: /* @__PURE__ */ jsx(CalendarClockIcon, { className: "size-3.5" }) }),
8435
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
8436
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [
8437
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
8438
+ /* @__PURE__ */ jsx("div", { className: "truncate text-foreground", children: title }),
8439
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex min-w-0 items-center gap-2", children: /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: formatKind(item.kind) }) }),
8440
+ 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)) })
8441
+ ] }),
8442
+ statusText && /* @__PURE__ */ jsx(Badge, { variant: "destructive", className: "shrink-0", children: statusText })
8443
+ ] }),
8444
+ item.lastError && /* @__PURE__ */ jsx("div", { className: "mt-1 line-clamp-2 text-xs text-destructive", children: item.lastError })
8445
+ ] }),
8446
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
8447
+ showRunNow && /* @__PURE__ */ jsx(
8448
+ "button",
8449
+ {
8450
+ type: "button",
8451
+ 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",
8452
+ onClick: (e) => {
8453
+ stopActionClick(e);
8454
+ onRunNow(item.id);
8455
+ },
8456
+ "aria-label": "Run schedule now",
8457
+ title: "Run schedule now",
8458
+ disabled,
8459
+ children: /* @__PURE__ */ jsx(CalendarClockIcon, { className: "size-3.5" })
8460
+ }
8461
+ ),
8462
+ canCancel && isActive && /* @__PURE__ */ jsx(
8463
+ "button",
8464
+ {
8465
+ type: "button",
8466
+ 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",
8467
+ onClick: (e) => {
8468
+ stopActionClick(e);
8469
+ onCancel(item.id);
8470
+ },
8471
+ "aria-label": "Cancel schedule",
8472
+ title: "Cancel schedule",
8473
+ disabled,
8474
+ children: /* @__PURE__ */ jsx(Trash2Icon, { className: "size-3.5" })
8475
+ }
8476
+ )
8477
+ ] })
8478
+ ]
8479
+ }
8480
+ );
8481
+ }
8482
+ function ScheduledWorkPanel({
8483
+ adapter,
8484
+ agentId,
8485
+ title = "Scheduled Work",
8486
+ limit = DEFAULT_LIMIT,
8487
+ status = "active",
8488
+ hideHeader = false,
8489
+ pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
8490
+ onSessionSelect,
8491
+ onScheduleCancel,
8492
+ onScheduleRunNow
8493
+ }) {
8494
+ const [items, setItems] = useState([]);
8495
+ const [error, setError] = useState(null);
8496
+ const [hasEverLoaded, setHasEverLoaded] = useState(false);
8497
+ const [actionScheduleId, setActionScheduleId] = useState(null);
8498
+ const [collapsed, setCollapsed] = useState(false);
8499
+ const mounted = useRef(true);
8500
+ const requestId = useRef(0);
8501
+ const lastFingerprint = useRef("");
8502
+ const refetch = useCallback(
8503
+ async (opts) => {
8504
+ if (!adapter.listScheduledWork) {
8505
+ setItems([]);
8506
+ setHasEverLoaded(true);
8507
+ return;
8508
+ }
8509
+ const currentRequestId = ++requestId.current;
8510
+ try {
8511
+ const list = await adapter.listScheduledWork({
8512
+ agentId,
8513
+ status,
8514
+ limit
8515
+ });
8516
+ if (!mounted.current || currentRequestId !== requestId.current) return;
8517
+ const nextItems = sortScheduledWork(list.items);
8518
+ const fp = fingerprint(nextItems);
8519
+ if (fp !== lastFingerprint.current) {
8520
+ lastFingerprint.current = fp;
8521
+ setItems(nextItems);
8522
+ }
8523
+ setError(null);
8524
+ setHasEverLoaded(true);
8525
+ } catch (e) {
8526
+ if (!mounted.current || currentRequestId !== requestId.current) return;
8527
+ if (!opts?.silent) {
8528
+ setError(
8529
+ e instanceof Error ? e.message : "Failed to load scheduled work"
8530
+ );
8531
+ setHasEverLoaded(true);
8532
+ }
8533
+ }
8534
+ },
8535
+ [adapter, agentId, limit, status]
8536
+ );
8537
+ useEffect(() => {
8538
+ mounted.current = true;
8539
+ return () => {
8540
+ mounted.current = false;
8541
+ };
8542
+ }, []);
8543
+ useEffect(() => {
8544
+ setError(null);
8545
+ lastFingerprint.current = "";
8546
+ void refetch();
8547
+ }, [refetch]);
8548
+ useEffect(() => {
8549
+ if (!adapter.listScheduledWork || pollIntervalMs <= 0) return;
8550
+ const id = setInterval(() => {
8551
+ void refetch({ silent: true });
8552
+ }, pollIntervalMs);
8553
+ return () => clearInterval(id);
8554
+ }, [adapter.listScheduledWork, pollIntervalMs, refetch]);
8555
+ const activeCount = useMemo(
8556
+ () => items.filter((item) => item.status === "active").length,
8557
+ [items]
8558
+ );
8559
+ const handleOpenLastRun = useCallback(
8560
+ (sessionId) => {
8561
+ onSessionSelect?.(sessionId);
8562
+ },
8563
+ [onSessionSelect]
8564
+ );
8565
+ const handleRunNow = useCallback(
8566
+ async (scheduleId) => {
8567
+ if (!adapter.runScheduledWorkNow || actionScheduleId) return;
8568
+ setActionScheduleId(scheduleId);
8569
+ try {
8570
+ const result = await adapter.runScheduledWorkNow({ scheduleId });
8571
+ onScheduleRunNow?.(scheduleId, result?.sessionId);
8572
+ await refetch({ silent: true });
8573
+ } catch (e) {
8574
+ setError(e instanceof Error ? e.message : "Failed to run schedule");
8575
+ } finally {
8576
+ if (mounted.current) setActionScheduleId(null);
8577
+ }
8578
+ },
8579
+ [actionScheduleId, adapter, onScheduleRunNow, refetch]
8580
+ );
8581
+ const handleCancel = useCallback(
8582
+ async (scheduleId) => {
8583
+ if (!adapter.cancelScheduledWork || actionScheduleId) return;
8584
+ setActionScheduleId(scheduleId);
8585
+ try {
8586
+ await adapter.cancelScheduledWork({ scheduleId });
8587
+ setItems((current) => {
8588
+ const next = current.filter((item) => item.id !== scheduleId);
8589
+ lastFingerprint.current = fingerprint(next);
8590
+ return next;
8591
+ });
8592
+ onScheduleCancel?.(scheduleId);
8593
+ await refetch({ silent: true });
8594
+ } catch (e) {
8595
+ setError(e instanceof Error ? e.message : "Failed to cancel schedule");
8596
+ } finally {
8597
+ if (mounted.current) setActionScheduleId(null);
8598
+ }
8599
+ },
8600
+ [actionScheduleId, adapter, onScheduleCancel, refetch]
8601
+ );
8602
+ if (!hasEverLoaded) return null;
8603
+ if (!adapter.listScheduledWork) return null;
8604
+ const showBody = hideHeader || !collapsed;
8605
+ return /* @__PURE__ */ jsxs("section", { className: "min-w-0 border-t border-line", children: [
8606
+ !hideHeader && /* @__PURE__ */ jsxs(
8607
+ "button",
8608
+ {
8609
+ type: "button",
8610
+ 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",
8611
+ onClick: () => setCollapsed((current) => !current),
8612
+ "aria-expanded": !collapsed,
8613
+ children: [
8614
+ /* @__PURE__ */ jsx(CalendarClockIcon, { className: "size-3.5" }),
8615
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: title }),
8616
+ activeCount > 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", className: "ml-auto", children: activeCount }),
8617
+ collapsed ? /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5 shrink-0 text-faint" }) : /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3.5 shrink-0 text-faint" })
8618
+ ]
8619
+ }
8620
+ ),
8621
+ showBody && error && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-destructive", children: error }),
8622
+ showBody && !error && items.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-faint", children: "No scheduled work" }),
8623
+ showBody && !error && items.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("pb-2", hideHeader && "pt-1"), children: items.map((item) => /* @__PURE__ */ jsx(
8624
+ ScheduledWorkRow,
8625
+ {
8626
+ item,
8627
+ canRunNow: Boolean(adapter.runScheduledWorkNow),
8628
+ canCancel: Boolean(adapter.cancelScheduledWork),
8629
+ actionScheduleId,
8630
+ onOpenLastRun: handleOpenLastRun,
8631
+ onRunNow: handleRunNow,
8632
+ onCancel: handleCancel
8633
+ },
8634
+ item.id
8635
+ )) })
8636
+ ] });
8637
+ }
8259
8638
  var POLL_INTERVAL_ACTIVE_MS = 4e3;
8260
8639
  var POLL_INTERVAL_IDLE_MS = 3e4;
8261
8640
  var DEFAULT_SESSION_LIST_LIMIT = 50;
@@ -8280,7 +8659,7 @@ function buildTree(nodes) {
8280
8659
  }
8281
8660
  function treeFingerprint(nodes) {
8282
8661
  return nodes.map(
8283
- (n) => `${n.id}:${n.parentId ?? ""}:${n.status}:${n.agentType ?? ""}:${n.messageCount ?? 0}:${n.toolCallCount ?? 0}:${n.updatedAt}`
8662
+ (n) => `${n.id}:${n.parentId ?? ""}:${n.status}:${n.agentType ?? ""}:${n.runKind ?? ""}:${n.title ?? ""}:${n.messageCount ?? 0}:${n.toolCallCount ?? 0}:${n.updatedAt}`
8284
8663
  ).join("|");
8285
8664
  }
8286
8665
  function sessionToTreeNode(session) {
@@ -8290,6 +8669,7 @@ function sessionToTreeNode(session) {
8290
8669
  parentId: session.parentId ?? null,
8291
8670
  agentId: session.agentId,
8292
8671
  channel: session.channel,
8672
+ runKind: session.runKind ?? deriveRunKind(session.channel, session.config),
8293
8673
  status: session.status,
8294
8674
  title: session.title,
8295
8675
  model: session.model,
@@ -8338,6 +8718,23 @@ function formatSessionTime(value) {
8338
8718
  if (Number.isNaN(date.getTime())) return "";
8339
8719
  return formatDistanceToNow(date, { addSuffix: true });
8340
8720
  }
8721
+ function deriveRunKind(channel, config) {
8722
+ if (channel === "scheduled" && config?.scheduled_dynamic_loop === true) {
8723
+ return "dynamic_loop";
8724
+ }
8725
+ if (channel === "scheduled") return "scheduled";
8726
+ return null;
8727
+ }
8728
+ function formatRunKind(value) {
8729
+ if (value === "dynamic_loop") return "Loop";
8730
+ if (value === "scheduled") return "Scheduled";
8731
+ return null;
8732
+ }
8733
+ function fallbackSessionTitle(entry) {
8734
+ if (entry.runKind === "dynamic_loop") return "Loop run";
8735
+ if (entry.runKind === "scheduled") return "Scheduled run";
8736
+ return "New session";
8737
+ }
8341
8738
  function TreeNodeRow({
8342
8739
  entry,
8343
8740
  depth,
@@ -8353,9 +8750,11 @@ function TreeNodeRow({
8353
8750
  const hasChildren = entry.children.length > 0;
8354
8751
  const isActive = entry.id === activeSessionId;
8355
8752
  const isRunning = entry.status === "active";
8356
- const isSubAgent = entry.parentId != null;
8357
- const title = entry.title ?? "New session";
8753
+ const isChildSession = entry.parentId != null;
8754
+ const title = entry.title ?? fallbackSessionTitle(entry);
8358
8755
  const subtitle = [
8756
+ formatRunKind(entry.runKind),
8757
+ entry.agentType,
8359
8758
  entry.model ?? "default",
8360
8759
  formatSessionTime(entry.updatedAt)
8361
8760
  ].filter(Boolean).join(" \xB7 ");
@@ -8393,7 +8792,7 @@ function TreeNodeRow({
8393
8792
  /* @__PURE__ */ jsx("div", { className: "text-sm truncate", children: title }),
8394
8793
  /* @__PURE__ */ jsx("div", { className: "text-xs text-faint truncate", children: subtitle })
8395
8794
  ] }),
8396
- isRunning && canStop && isSubAgent && /* @__PURE__ */ jsx(
8795
+ isRunning && canStop && isChildSession && /* @__PURE__ */ jsx(
8397
8796
  "button",
8398
8797
  {
8399
8798
  type: "button",
@@ -8402,8 +8801,8 @@ function TreeNodeRow({
8402
8801
  e.stopPropagation();
8403
8802
  onStop(entry.id);
8404
8803
  },
8405
- "aria-label": "Stop sub-agent",
8406
- title: "Stop sub-agent",
8804
+ "aria-label": "Stop child session",
8805
+ title: "Stop child session",
8407
8806
  children: /* @__PURE__ */ jsx(SquareIcon, { className: "w-3 h-3", fill: "currentColor" })
8408
8807
  }
8409
8808
  ),
@@ -8604,6 +9003,6 @@ function SessionTreePanel({
8604
9003
  ] });
8605
9004
  }
8606
9005
 
8607
- export { AgentChat, AgentChatAdapterProvider, MessageResponse, SessionTreePanel, useAgentChatAdapterContext, useAgentChatRuntime };
9006
+ export { AgentChat, AgentChatAdapterProvider, MessageResponse, ScheduledWorkPanel, SessionTreePanel, useAgentChatAdapterContext, useAgentChatRuntime };
8608
9007
  //# sourceMappingURL=index.js.map
8609
9008
  //# sourceMappingURL=index.js.map