@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/README.md +13 -0
- package/dist/index.cjs +430 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -1
- package/dist/index.d.ts +60 -1
- package/dist/index.js +430 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3552,13 +3552,16 @@ function ClarifyLocked({
|
|
|
3552
3552
|
}) })
|
|
3553
3553
|
] });
|
|
3554
3554
|
}
|
|
3555
|
-
function ArtifactToolBlock({
|
|
3556
|
-
|
|
3555
|
+
function ArtifactToolBlock({
|
|
3556
|
+
tc,
|
|
3557
|
+
resolvedName
|
|
3558
|
+
}) {
|
|
3557
3559
|
const status = effectiveStatus(tc);
|
|
3558
3560
|
const label = status === "running" ? "Creating artifact\u2026" : status === "error" ? "Creating artifact\u2026" : "Created";
|
|
3561
|
+
const name = resolvedName ?? parseArgs(tc.args)?.name;
|
|
3559
3562
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-sm ", children: [
|
|
3560
3563
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: label }),
|
|
3561
|
-
|
|
3564
|
+
name && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground truncate", children: name })
|
|
3562
3565
|
] });
|
|
3563
3566
|
}
|
|
3564
3567
|
|
|
@@ -3891,7 +3894,11 @@ function DefaultToolBlock({ tc }) {
|
|
|
3891
3894
|
] })
|
|
3892
3895
|
] });
|
|
3893
3896
|
}
|
|
3894
|
-
function ToolCallBlock({
|
|
3897
|
+
function ToolCallBlock({
|
|
3898
|
+
tc,
|
|
3899
|
+
resolvedArtifactName,
|
|
3900
|
+
onFileSelect
|
|
3901
|
+
}) {
|
|
3895
3902
|
switch (tc.toolName) {
|
|
3896
3903
|
case "terminal":
|
|
3897
3904
|
return /* @__PURE__ */ jsxRuntime.jsx(TerminalToolBlock, { tc });
|
|
@@ -3928,7 +3935,7 @@ function ToolCallBlock({ tc, onFileSelect }) {
|
|
|
3928
3935
|
case "clarify":
|
|
3929
3936
|
return /* @__PURE__ */ jsxRuntime.jsx(ClarifyToolBlock, { tc });
|
|
3930
3937
|
case "create_artifact":
|
|
3931
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ArtifactToolBlock, { tc });
|
|
3938
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ArtifactToolBlock, { tc, resolvedName: resolvedArtifactName });
|
|
3932
3939
|
case "delegate_task":
|
|
3933
3940
|
return /* @__PURE__ */ jsxRuntime.jsx(DelegateToolBlock, { tc });
|
|
3934
3941
|
case "memory":
|
|
@@ -5337,6 +5344,7 @@ function ChatComposerInner({
|
|
|
5337
5344
|
onStop,
|
|
5338
5345
|
isRunning,
|
|
5339
5346
|
disabled = false,
|
|
5347
|
+
disabledReason,
|
|
5340
5348
|
tokenUsage
|
|
5341
5349
|
}) {
|
|
5342
5350
|
const { adapter } = useAgentChatAdapterContext();
|
|
@@ -5365,7 +5373,10 @@ function ChatComposerInner({
|
|
|
5365
5373
|
const slashCommands = react.useMemo(() => {
|
|
5366
5374
|
const builtin = [
|
|
5367
5375
|
{ value: "/clear", label: "/clear", description: "Clear conversation" },
|
|
5368
|
-
{ value: "/compress", label: "/compress", description: "Compress context" }
|
|
5376
|
+
{ value: "/compress", label: "/compress", description: "Compress context" },
|
|
5377
|
+
{ value: "/loop", label: "/loop", description: "Schedule recurring prompt" },
|
|
5378
|
+
{ value: "/loop list", label: "/loop list", description: "List active loops" },
|
|
5379
|
+
{ value: "/loop cancel", label: "/loop cancel", description: "Cancel a loop by ID" }
|
|
5369
5380
|
];
|
|
5370
5381
|
return [...adapterCommands, ...builtin];
|
|
5371
5382
|
}, [adapterCommands]);
|
|
@@ -5444,7 +5455,7 @@ function ChatComposerInner({
|
|
|
5444
5455
|
/* @__PURE__ */ jsxRuntime.jsx(PromptInputBody, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5445
5456
|
PromptInputTextarea,
|
|
5446
5457
|
{
|
|
5447
|
-
placeholder: disabled ? "Session disabled" : "Send a message...",
|
|
5458
|
+
placeholder: disabled ? disabledReason ?? "Session disabled" : "Send a message...",
|
|
5448
5459
|
disabled,
|
|
5449
5460
|
onKeyDown: handleKeyDown
|
|
5450
5461
|
}
|
|
@@ -6099,7 +6110,7 @@ var ErrorMessage = react.memo(function ErrorMessage2({
|
|
|
6099
6110
|
|
|
6100
6111
|
// src/components/chat/chat-thread.tsx
|
|
6101
6112
|
init_utils();
|
|
6102
|
-
function messageToEntries(msg, isLast) {
|
|
6113
|
+
function messageToEntries(msg, isLast, artifactFallbacks) {
|
|
6103
6114
|
if (msg.role === "system") {
|
|
6104
6115
|
if (msg.systemKind === "skill_invoked") {
|
|
6105
6116
|
return [{
|
|
@@ -6141,6 +6152,18 @@ function messageToEntries(msg, isLast) {
|
|
|
6141
6152
|
}
|
|
6142
6153
|
if (hasToolCalls) {
|
|
6143
6154
|
for (const tc of msg.toolCalls) {
|
|
6155
|
+
if (tc.toolName === "create_artifact") {
|
|
6156
|
+
const resolvedName = parseArgs(tc.args)?.name ?? artifactFallbacks[tc.id];
|
|
6157
|
+
const status = effectiveStatus(tc);
|
|
6158
|
+
if (!resolvedName && status !== "error") continue;
|
|
6159
|
+
entries.push({
|
|
6160
|
+
kind: "tool",
|
|
6161
|
+
key: tc.id,
|
|
6162
|
+
tc,
|
|
6163
|
+
resolvedArtifactName: resolvedName
|
|
6164
|
+
});
|
|
6165
|
+
continue;
|
|
6166
|
+
}
|
|
6144
6167
|
entries.push({ kind: "tool", key: tc.id, tc });
|
|
6145
6168
|
}
|
|
6146
6169
|
}
|
|
@@ -6299,7 +6322,14 @@ function TimelineEntryItem({
|
|
|
6299
6322
|
)
|
|
6300
6323
|
] }),
|
|
6301
6324
|
/* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: entry.tc.cancelled ? /* @__PURE__ */ jsxRuntime.jsx(CancelledToolRow, { tc: entry.tc }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
6302
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6325
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6326
|
+
ToolCallBlock,
|
|
6327
|
+
{
|
|
6328
|
+
tc: entry.tc,
|
|
6329
|
+
resolvedArtifactName: entry.resolvedArtifactName,
|
|
6330
|
+
onFileSelect
|
|
6331
|
+
}
|
|
6332
|
+
),
|
|
6303
6333
|
failureSummary ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-full truncate text-xs text-destructive", title: failureSummary, children: failureSummary }) : null
|
|
6304
6334
|
] }) })
|
|
6305
6335
|
] });
|
|
@@ -6361,13 +6391,14 @@ function AssistantGroup({
|
|
|
6361
6391
|
totalMessages,
|
|
6362
6392
|
isRunning,
|
|
6363
6393
|
sessionId,
|
|
6394
|
+
artifactFallbacks,
|
|
6364
6395
|
onFileSelect,
|
|
6365
6396
|
onRetry
|
|
6366
6397
|
}) {
|
|
6367
6398
|
const entries = [];
|
|
6368
6399
|
for (let i = 0; i < messages.length; i++) {
|
|
6369
6400
|
const isLast = i === messages.length - 1 && lastGlobalIndex === totalMessages - 1;
|
|
6370
|
-
entries.push(...messageToEntries(messages[i], isLast));
|
|
6401
|
+
entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
|
|
6371
6402
|
}
|
|
6372
6403
|
const isTailGroup = lastGlobalIndex === totalMessages - 1;
|
|
6373
6404
|
const lastEntry = entries[entries.length - 1];
|
|
@@ -6434,11 +6465,34 @@ function ChatThread({
|
|
|
6434
6465
|
onStop,
|
|
6435
6466
|
onFileSelect,
|
|
6436
6467
|
disabled = false,
|
|
6468
|
+
disabledReason,
|
|
6437
6469
|
tokenUsage,
|
|
6438
6470
|
retryIndicator,
|
|
6439
6471
|
onRetry
|
|
6440
6472
|
}) {
|
|
6441
6473
|
const groups = react.useMemo(() => groupMessages(messages), [messages]);
|
|
6474
|
+
const artifactFallbacks = react.useMemo(() => {
|
|
6475
|
+
const fallbacks = {};
|
|
6476
|
+
const createArtifactToolCallIds = [];
|
|
6477
|
+
let artifactIdx = 0;
|
|
6478
|
+
for (const msg of messages) {
|
|
6479
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
6480
|
+
for (const tc of msg.toolCalls) {
|
|
6481
|
+
if (tc.toolName === "create_artifact" && !tc.cancelled && tc.status !== "error") {
|
|
6482
|
+
createArtifactToolCallIds.push(tc.id);
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
} else if (msg.role === "system" && msg.systemKind === "artifact") {
|
|
6486
|
+
const tcId = createArtifactToolCallIds[artifactIdx];
|
|
6487
|
+
if (tcId) {
|
|
6488
|
+
const { name } = unpackArtifactMeta(msg.systemMeta, msg.content);
|
|
6489
|
+
if (name) fallbacks[tcId] = name;
|
|
6490
|
+
}
|
|
6491
|
+
artifactIdx += 1;
|
|
6492
|
+
}
|
|
6493
|
+
}
|
|
6494
|
+
return fallbacks;
|
|
6495
|
+
}, [messages]);
|
|
6442
6496
|
const activeFailureId = react.useMemo(() => {
|
|
6443
6497
|
if (messages.length === 0) return null;
|
|
6444
6498
|
const tail = messages[messages.length - 1];
|
|
@@ -6503,6 +6557,7 @@ function ChatThread({
|
|
|
6503
6557
|
totalMessages: messages.length,
|
|
6504
6558
|
isRunning,
|
|
6505
6559
|
sessionId,
|
|
6560
|
+
artifactFallbacks,
|
|
6506
6561
|
onFileSelect,
|
|
6507
6562
|
onRetry: groupRetry
|
|
6508
6563
|
},
|
|
@@ -6515,13 +6570,21 @@ function ChatThread({
|
|
|
6515
6570
|
] }),
|
|
6516
6571
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-4xl px-6 pb-5 pt-3", children: [
|
|
6517
6572
|
retryIndicator && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(RetryBanner, { indicator: retryIndicator }) }),
|
|
6518
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6573
|
+
disabled && disabledReason ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6574
|
+
"div",
|
|
6575
|
+
{
|
|
6576
|
+
className: "rounded border border-line bg-muted/40 px-3 py-2 text-sm text-muted-foreground",
|
|
6577
|
+
role: "status",
|
|
6578
|
+
children: disabledReason
|
|
6579
|
+
}
|
|
6580
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
6519
6581
|
ChatComposer,
|
|
6520
6582
|
{
|
|
6521
6583
|
onSend,
|
|
6522
6584
|
onStop,
|
|
6523
6585
|
isRunning,
|
|
6524
6586
|
disabled,
|
|
6587
|
+
disabledReason,
|
|
6525
6588
|
tokenUsage
|
|
6526
6589
|
}
|
|
6527
6590
|
)
|
|
@@ -7359,7 +7422,7 @@ function WorkspacePanel({
|
|
|
7359
7422
|
);
|
|
7360
7423
|
const handleDelete = react.useCallback(
|
|
7361
7424
|
async (path) => {
|
|
7362
|
-
if (!sessionId) return;
|
|
7425
|
+
if (disabled || !sessionId) return;
|
|
7363
7426
|
try {
|
|
7364
7427
|
await adapter.deleteWorkspaceFile({ sessionId, path });
|
|
7365
7428
|
if (selectedPath === path) {
|
|
@@ -7375,7 +7438,7 @@ function WorkspacePanel({
|
|
|
7375
7438
|
setDeleteTarget(null);
|
|
7376
7439
|
}
|
|
7377
7440
|
},
|
|
7378
|
-
[adapter, fetchTree, onSelectedPathChange, selectedPath, sessionId]
|
|
7441
|
+
[adapter, disabled, fetchTree, onSelectedPathChange, selectedPath, sessionId]
|
|
7379
7442
|
);
|
|
7380
7443
|
const onResizeStart = react.useCallback(
|
|
7381
7444
|
(event) => {
|
|
@@ -7458,7 +7521,7 @@ function WorkspacePanel({
|
|
|
7458
7521
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpenIcon, { className: "size-4 shrink-0 text-amber-500" }),
|
|
7459
7522
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
7460
7523
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-sm font-medium text-foreground", children: rootName }),
|
|
7461
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs text-faint", children: "Workspace" })
|
|
7524
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs text-faint", children: disabled ? "Workspace - Read-only" : "Workspace" })
|
|
7462
7525
|
] }),
|
|
7463
7526
|
/* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
7464
7527
|
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -7569,7 +7632,7 @@ function WorkspacePanel({
|
|
|
7569
7632
|
sessionId,
|
|
7570
7633
|
path: file.path
|
|
7571
7634
|
}) : null,
|
|
7572
|
-
onDelete: file ? () => setDeleteTarget(file.path) : null,
|
|
7635
|
+
onDelete: file && !disabled ? () => setDeleteTarget(file.path) : null,
|
|
7573
7636
|
onClose: () => {
|
|
7574
7637
|
onSelectedPathChange(null);
|
|
7575
7638
|
setFile(null);
|
|
@@ -7633,6 +7696,13 @@ function ConfirmDialog({
|
|
|
7633
7696
|
);
|
|
7634
7697
|
}
|
|
7635
7698
|
|
|
7699
|
+
// src/lib/sessions.ts
|
|
7700
|
+
function isScheduledRunSession(session) {
|
|
7701
|
+
return Boolean(
|
|
7702
|
+
session?.channel === "scheduled" || session?.config?.scheduled_session_id
|
|
7703
|
+
);
|
|
7704
|
+
}
|
|
7705
|
+
|
|
7636
7706
|
// src/runtime/events.ts
|
|
7637
7707
|
var WORKSPACE_MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
7638
7708
|
"terminal",
|
|
@@ -8245,6 +8315,7 @@ function useAgentChatRuntime({
|
|
|
8245
8315
|
const [state, setState] = react.useState(
|
|
8246
8316
|
() => createInitialAgentChatState({ isLoadingHistory: Boolean(sessionId) })
|
|
8247
8317
|
);
|
|
8318
|
+
const [session, setSession] = react.useState(null);
|
|
8248
8319
|
const stateRef = react.useRef(state);
|
|
8249
8320
|
const streamRef = react.useRef(null);
|
|
8250
8321
|
const reconnectTimerRef = react.useRef(null);
|
|
@@ -8269,6 +8340,7 @@ function useAgentChatRuntime({
|
|
|
8269
8340
|
clearReconnectTimer();
|
|
8270
8341
|
closeStream();
|
|
8271
8342
|
if (!sessionId) {
|
|
8343
|
+
setSession(null);
|
|
8272
8344
|
setState(createInitialAgentChatState());
|
|
8273
8345
|
return;
|
|
8274
8346
|
}
|
|
@@ -8316,17 +8388,19 @@ function useAgentChatRuntime({
|
|
|
8316
8388
|
isLoadingHistory: true
|
|
8317
8389
|
});
|
|
8318
8390
|
stateRef.current = initialState;
|
|
8391
|
+
setSession(null);
|
|
8319
8392
|
setState(initialState);
|
|
8320
8393
|
connect(0);
|
|
8321
|
-
adapter.getSession({ sessionId }).then((
|
|
8394
|
+
adapter.getSession({ sessionId }).then((loadedSession) => {
|
|
8322
8395
|
if (cancelled) return;
|
|
8323
|
-
|
|
8396
|
+
setSession(loadedSession);
|
|
8397
|
+
if (loadedSession.messageCount === 0) {
|
|
8324
8398
|
setState((prev) => ({
|
|
8325
8399
|
...prev,
|
|
8326
8400
|
isLoadingHistory: false
|
|
8327
8401
|
}));
|
|
8328
8402
|
}
|
|
8329
|
-
if (isTerminalStatus(
|
|
8403
|
+
if (isTerminalStatus(loadedSession.status)) {
|
|
8330
8404
|
setState((prev) => ({
|
|
8331
8405
|
...prev,
|
|
8332
8406
|
terminal: true,
|
|
@@ -8415,9 +8489,9 @@ function useAgentChatRuntime({
|
|
|
8415
8489
|
markSending(content, images);
|
|
8416
8490
|
if (!sessionId) {
|
|
8417
8491
|
try {
|
|
8418
|
-
const
|
|
8419
|
-
onSessionChange?.(
|
|
8420
|
-
await adapter.sendMessage({ sessionId:
|
|
8492
|
+
const session2 = await adapter.createSession({ agentId });
|
|
8493
|
+
onSessionChange?.(session2.id);
|
|
8494
|
+
await adapter.sendMessage({ sessionId: session2.id, content, images });
|
|
8421
8495
|
} catch (error) {
|
|
8422
8496
|
markSendError(error instanceof Error ? error.message : "send failed");
|
|
8423
8497
|
throw error;
|
|
@@ -8458,6 +8532,7 @@ function useAgentChatRuntime({
|
|
|
8458
8532
|
}
|
|
8459
8533
|
}, [adapter, sessionId]);
|
|
8460
8534
|
return {
|
|
8535
|
+
session,
|
|
8461
8536
|
messages: state.messages,
|
|
8462
8537
|
isRunning: state.isRunning,
|
|
8463
8538
|
isLoadingHistory: state.isLoadingHistory,
|
|
@@ -8505,6 +8580,9 @@ function AgentChat({
|
|
|
8505
8580
|
sessionId,
|
|
8506
8581
|
onSessionChange
|
|
8507
8582
|
});
|
|
8583
|
+
const isReadOnlySession = isScheduledRunSession(runtime.session);
|
|
8584
|
+
const effectiveDisabled = disabled || isReadOnlySession;
|
|
8585
|
+
const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
|
|
8508
8586
|
react.useEffect(() => {
|
|
8509
8587
|
onMessagesChange?.(runtime.messages);
|
|
8510
8588
|
}, [onMessagesChange, runtime.messages]);
|
|
@@ -8535,7 +8613,8 @@ function AgentChat({
|
|
|
8535
8613
|
onStop: () => void runtime.stop(),
|
|
8536
8614
|
onRetry: runtime.retry,
|
|
8537
8615
|
onFileSelect: handleFileSelect,
|
|
8538
|
-
disabled,
|
|
8616
|
+
disabled: effectiveDisabled,
|
|
8617
|
+
disabledReason,
|
|
8539
8618
|
tokenUsage: runtime.tokenUsage,
|
|
8540
8619
|
retryIndicator: runtime.retryIndicator
|
|
8541
8620
|
}
|
|
@@ -8550,7 +8629,7 @@ function AgentChat({
|
|
|
8550
8629
|
collapsed: workspaceCollapsed,
|
|
8551
8630
|
onCollapsedChange: setWorkspaceCollapsed,
|
|
8552
8631
|
refreshSignal: runtime.workspaceRefreshKey,
|
|
8553
|
-
disabled
|
|
8632
|
+
disabled: effectiveDisabled
|
|
8554
8633
|
}
|
|
8555
8634
|
)
|
|
8556
8635
|
] }) })
|
|
@@ -8558,6 +8637,307 @@ function AgentChat({
|
|
|
8558
8637
|
);
|
|
8559
8638
|
}
|
|
8560
8639
|
init_utils();
|
|
8640
|
+
var DEFAULT_LIMIT = 50;
|
|
8641
|
+
var DEFAULT_POLL_INTERVAL_MS = 3e4;
|
|
8642
|
+
function fingerprint(items) {
|
|
8643
|
+
return items.map(
|
|
8644
|
+
(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}`
|
|
8645
|
+
).join("|");
|
|
8646
|
+
}
|
|
8647
|
+
function formatKind(value) {
|
|
8648
|
+
if (value === "dynamic_loop") return "Dynamic loop";
|
|
8649
|
+
if (value === "cron") return "Cron";
|
|
8650
|
+
if (value === "one_shot") return "One-shot";
|
|
8651
|
+
if (value === "scheduled") return "Scheduled";
|
|
8652
|
+
if (!value) return "Scheduled";
|
|
8653
|
+
return value.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
8654
|
+
}
|
|
8655
|
+
function formatRelative(value, label) {
|
|
8656
|
+
if (!value) return null;
|
|
8657
|
+
const date = new Date(value);
|
|
8658
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
8659
|
+
return `${label} ${dateFns.formatDistanceToNow(date, { addSuffix: true })}`;
|
|
8660
|
+
}
|
|
8661
|
+
function formatRuns(count) {
|
|
8662
|
+
return `${count} ${count === 1 ? "run" : "runs"}`;
|
|
8663
|
+
}
|
|
8664
|
+
function scheduleTitle(item) {
|
|
8665
|
+
const title = item.name?.trim();
|
|
8666
|
+
if (title) return title;
|
|
8667
|
+
return item.prompt;
|
|
8668
|
+
}
|
|
8669
|
+
function canRunScheduleNow(item) {
|
|
8670
|
+
return item.repeatLimit === 1;
|
|
8671
|
+
}
|
|
8672
|
+
function scheduleDisplay(item) {
|
|
8673
|
+
if (item.kind === "dynamic_loop") return null;
|
|
8674
|
+
return item.scheduleDisplay;
|
|
8675
|
+
}
|
|
8676
|
+
function sortScheduledWork(items) {
|
|
8677
|
+
return [...items].sort((a, b) => {
|
|
8678
|
+
if (a.status === "active" && b.status !== "active") return -1;
|
|
8679
|
+
if (a.status !== "active" && b.status === "active") return 1;
|
|
8680
|
+
const aNext = a.nextRunAt ?? "";
|
|
8681
|
+
const bNext = b.nextRunAt ?? "";
|
|
8682
|
+
if (aNext && bNext && aNext !== bNext) return aNext < bNext ? -1 : 1;
|
|
8683
|
+
if (aNext && !bNext) return -1;
|
|
8684
|
+
if (!aNext && bNext) return 1;
|
|
8685
|
+
return (a.updatedAt ?? "") > (b.updatedAt ?? "") ? -1 : 1;
|
|
8686
|
+
});
|
|
8687
|
+
}
|
|
8688
|
+
function ScheduledWorkRow({
|
|
8689
|
+
item,
|
|
8690
|
+
canRunNow,
|
|
8691
|
+
canCancel,
|
|
8692
|
+
actionScheduleId,
|
|
8693
|
+
onOpenLastRun,
|
|
8694
|
+
onRunNow,
|
|
8695
|
+
onCancel
|
|
8696
|
+
}) {
|
|
8697
|
+
const isActive = item.status === "active";
|
|
8698
|
+
const disabled = actionScheduleId === item.id;
|
|
8699
|
+
const title = scheduleTitle(item);
|
|
8700
|
+
const statusText = item.status === "active" ? null : item.status;
|
|
8701
|
+
const showRunNow = canRunNow && isActive && canRunScheduleNow(item);
|
|
8702
|
+
const lastSessionId = item.lastSessionId ?? null;
|
|
8703
|
+
const meta = [
|
|
8704
|
+
scheduleDisplay(item),
|
|
8705
|
+
formatRelative(item.nextRunAt, "Next"),
|
|
8706
|
+
formatRelative(item.lastRunAt, "Last"),
|
|
8707
|
+
formatRuns(item.runCount),
|
|
8708
|
+
item.expiresAt ? formatRelative(item.expiresAt, "Expires") : null
|
|
8709
|
+
].filter(Boolean);
|
|
8710
|
+
const openLastRun = () => {
|
|
8711
|
+
if (lastSessionId) onOpenLastRun(lastSessionId);
|
|
8712
|
+
};
|
|
8713
|
+
const handleRowKeyDown = (e) => {
|
|
8714
|
+
if (!lastSessionId) return;
|
|
8715
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
8716
|
+
e.preventDefault();
|
|
8717
|
+
openLastRun();
|
|
8718
|
+
};
|
|
8719
|
+
const stopActionClick = (e) => {
|
|
8720
|
+
e.stopPropagation();
|
|
8721
|
+
};
|
|
8722
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8723
|
+
"div",
|
|
8724
|
+
{
|
|
8725
|
+
className: cn(
|
|
8726
|
+
"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",
|
|
8727
|
+
lastSessionId && "cursor-pointer"
|
|
8728
|
+
),
|
|
8729
|
+
onClick: openLastRun,
|
|
8730
|
+
onKeyDown: handleRowKeyDown,
|
|
8731
|
+
role: lastSessionId ? "button" : void 0,
|
|
8732
|
+
tabIndex: lastSessionId ? 0 : void 0,
|
|
8733
|
+
"aria-label": lastSessionId ? `Open last run for ${title}` : void 0,
|
|
8734
|
+
title: lastSessionId ? "Open last run" : void 0,
|
|
8735
|
+
children: [
|
|
8736
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-0.5 flex size-5 shrink-0 items-center justify-center text-faint", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarClockIcon, { className: "size-3.5" }) }),
|
|
8737
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
8738
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [
|
|
8739
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
8740
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-foreground", children: title }),
|
|
8741
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-0.5 flex min-w-0 items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "secondary", className: "shrink-0", children: formatKind(item.kind) }) }),
|
|
8742
|
+
meta.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-0.5 space-y-0.5 text-xs text-faint", children: meta.map((line) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate", children: line }, line)) })
|
|
8743
|
+
] }),
|
|
8744
|
+
statusText && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "destructive", className: "shrink-0", children: statusText })
|
|
8745
|
+
] }),
|
|
8746
|
+
item.lastError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 line-clamp-2 text-xs text-destructive", children: item.lastError })
|
|
8747
|
+
] }),
|
|
8748
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
|
|
8749
|
+
showRunNow && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8750
|
+
"button",
|
|
8751
|
+
{
|
|
8752
|
+
type: "button",
|
|
8753
|
+
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",
|
|
8754
|
+
onClick: (e) => {
|
|
8755
|
+
stopActionClick(e);
|
|
8756
|
+
onRunNow(item.id);
|
|
8757
|
+
},
|
|
8758
|
+
"aria-label": "Run schedule now",
|
|
8759
|
+
title: "Run schedule now",
|
|
8760
|
+
disabled,
|
|
8761
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarClockIcon, { className: "size-3.5" })
|
|
8762
|
+
}
|
|
8763
|
+
),
|
|
8764
|
+
canCancel && isActive && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8765
|
+
"button",
|
|
8766
|
+
{
|
|
8767
|
+
type: "button",
|
|
8768
|
+
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",
|
|
8769
|
+
onClick: (e) => {
|
|
8770
|
+
stopActionClick(e);
|
|
8771
|
+
onCancel(item.id);
|
|
8772
|
+
},
|
|
8773
|
+
"aria-label": "Cancel schedule",
|
|
8774
|
+
title: "Cancel schedule",
|
|
8775
|
+
disabled,
|
|
8776
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2Icon, { className: "size-3.5" })
|
|
8777
|
+
}
|
|
8778
|
+
)
|
|
8779
|
+
] })
|
|
8780
|
+
]
|
|
8781
|
+
}
|
|
8782
|
+
);
|
|
8783
|
+
}
|
|
8784
|
+
function ScheduledWorkPanel({
|
|
8785
|
+
adapter,
|
|
8786
|
+
agentId,
|
|
8787
|
+
title = "Scheduled Work",
|
|
8788
|
+
limit = DEFAULT_LIMIT,
|
|
8789
|
+
status = "active",
|
|
8790
|
+
hideHeader = false,
|
|
8791
|
+
pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
|
|
8792
|
+
onSessionSelect,
|
|
8793
|
+
onScheduleCancel,
|
|
8794
|
+
onScheduleRunNow
|
|
8795
|
+
}) {
|
|
8796
|
+
const [items, setItems] = react.useState([]);
|
|
8797
|
+
const [error, setError] = react.useState(null);
|
|
8798
|
+
const [hasEverLoaded, setHasEverLoaded] = react.useState(false);
|
|
8799
|
+
const [actionScheduleId, setActionScheduleId] = react.useState(null);
|
|
8800
|
+
const [collapsed, setCollapsed] = react.useState(false);
|
|
8801
|
+
const mounted = react.useRef(true);
|
|
8802
|
+
const requestId = react.useRef(0);
|
|
8803
|
+
const lastFingerprint = react.useRef("");
|
|
8804
|
+
const refetch = react.useCallback(
|
|
8805
|
+
async (opts) => {
|
|
8806
|
+
if (!adapter.listScheduledWork) {
|
|
8807
|
+
setItems([]);
|
|
8808
|
+
setHasEverLoaded(true);
|
|
8809
|
+
return;
|
|
8810
|
+
}
|
|
8811
|
+
const currentRequestId = ++requestId.current;
|
|
8812
|
+
try {
|
|
8813
|
+
const list = await adapter.listScheduledWork({
|
|
8814
|
+
agentId,
|
|
8815
|
+
status,
|
|
8816
|
+
limit
|
|
8817
|
+
});
|
|
8818
|
+
if (!mounted.current || currentRequestId !== requestId.current) return;
|
|
8819
|
+
const nextItems = sortScheduledWork(list.items);
|
|
8820
|
+
const fp = fingerprint(nextItems);
|
|
8821
|
+
if (fp !== lastFingerprint.current) {
|
|
8822
|
+
lastFingerprint.current = fp;
|
|
8823
|
+
setItems(nextItems);
|
|
8824
|
+
}
|
|
8825
|
+
setError(null);
|
|
8826
|
+
setHasEverLoaded(true);
|
|
8827
|
+
} catch (e) {
|
|
8828
|
+
if (!mounted.current || currentRequestId !== requestId.current) return;
|
|
8829
|
+
if (!opts?.silent) {
|
|
8830
|
+
setError(
|
|
8831
|
+
e instanceof Error ? e.message : "Failed to load scheduled work"
|
|
8832
|
+
);
|
|
8833
|
+
setHasEverLoaded(true);
|
|
8834
|
+
}
|
|
8835
|
+
}
|
|
8836
|
+
},
|
|
8837
|
+
[adapter, agentId, limit, status]
|
|
8838
|
+
);
|
|
8839
|
+
react.useEffect(() => {
|
|
8840
|
+
mounted.current = true;
|
|
8841
|
+
return () => {
|
|
8842
|
+
mounted.current = false;
|
|
8843
|
+
};
|
|
8844
|
+
}, []);
|
|
8845
|
+
react.useEffect(() => {
|
|
8846
|
+
setError(null);
|
|
8847
|
+
lastFingerprint.current = "";
|
|
8848
|
+
void refetch();
|
|
8849
|
+
}, [refetch]);
|
|
8850
|
+
react.useEffect(() => {
|
|
8851
|
+
if (!adapter.listScheduledWork || pollIntervalMs <= 0) return;
|
|
8852
|
+
const id = setInterval(() => {
|
|
8853
|
+
void refetch({ silent: true });
|
|
8854
|
+
}, pollIntervalMs);
|
|
8855
|
+
return () => clearInterval(id);
|
|
8856
|
+
}, [adapter.listScheduledWork, pollIntervalMs, refetch]);
|
|
8857
|
+
const activeCount = react.useMemo(
|
|
8858
|
+
() => items.filter((item) => item.status === "active").length,
|
|
8859
|
+
[items]
|
|
8860
|
+
);
|
|
8861
|
+
const handleOpenLastRun = react.useCallback(
|
|
8862
|
+
(sessionId) => {
|
|
8863
|
+
onSessionSelect?.(sessionId);
|
|
8864
|
+
},
|
|
8865
|
+
[onSessionSelect]
|
|
8866
|
+
);
|
|
8867
|
+
const handleRunNow = react.useCallback(
|
|
8868
|
+
async (scheduleId) => {
|
|
8869
|
+
if (!adapter.runScheduledWorkNow || actionScheduleId) return;
|
|
8870
|
+
setActionScheduleId(scheduleId);
|
|
8871
|
+
try {
|
|
8872
|
+
const result = await adapter.runScheduledWorkNow({ scheduleId });
|
|
8873
|
+
onScheduleRunNow?.(scheduleId, result?.sessionId);
|
|
8874
|
+
await refetch({ silent: true });
|
|
8875
|
+
} catch (e) {
|
|
8876
|
+
setError(e instanceof Error ? e.message : "Failed to run schedule");
|
|
8877
|
+
} finally {
|
|
8878
|
+
if (mounted.current) setActionScheduleId(null);
|
|
8879
|
+
}
|
|
8880
|
+
},
|
|
8881
|
+
[actionScheduleId, adapter, onScheduleRunNow, refetch]
|
|
8882
|
+
);
|
|
8883
|
+
const handleCancel = react.useCallback(
|
|
8884
|
+
async (scheduleId) => {
|
|
8885
|
+
if (!adapter.cancelScheduledWork || actionScheduleId) return;
|
|
8886
|
+
setActionScheduleId(scheduleId);
|
|
8887
|
+
try {
|
|
8888
|
+
await adapter.cancelScheduledWork({ scheduleId });
|
|
8889
|
+
setItems((current) => {
|
|
8890
|
+
const next = current.filter((item) => item.id !== scheduleId);
|
|
8891
|
+
lastFingerprint.current = fingerprint(next);
|
|
8892
|
+
return next;
|
|
8893
|
+
});
|
|
8894
|
+
onScheduleCancel?.(scheduleId);
|
|
8895
|
+
await refetch({ silent: true });
|
|
8896
|
+
} catch (e) {
|
|
8897
|
+
setError(e instanceof Error ? e.message : "Failed to cancel schedule");
|
|
8898
|
+
} finally {
|
|
8899
|
+
if (mounted.current) setActionScheduleId(null);
|
|
8900
|
+
}
|
|
8901
|
+
},
|
|
8902
|
+
[actionScheduleId, adapter, onScheduleCancel, refetch]
|
|
8903
|
+
);
|
|
8904
|
+
if (!hasEverLoaded) return null;
|
|
8905
|
+
if (!adapter.listScheduledWork) return null;
|
|
8906
|
+
const showBody = hideHeader || !collapsed;
|
|
8907
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "min-w-0 border-t border-line", children: [
|
|
8908
|
+
!hideHeader && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8909
|
+
"button",
|
|
8910
|
+
{
|
|
8911
|
+
type: "button",
|
|
8912
|
+
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",
|
|
8913
|
+
onClick: () => setCollapsed((current) => !current),
|
|
8914
|
+
"aria-expanded": !collapsed,
|
|
8915
|
+
children: [
|
|
8916
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarClockIcon, { className: "size-3.5" }),
|
|
8917
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: title }),
|
|
8918
|
+
activeCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", className: "ml-auto", children: activeCount }),
|
|
8919
|
+
collapsed ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "size-3.5 shrink-0 text-faint" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDownIcon, { className: "size-3.5 shrink-0 text-faint" })
|
|
8920
|
+
]
|
|
8921
|
+
}
|
|
8922
|
+
),
|
|
8923
|
+
showBody && error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-xs text-destructive", children: error }),
|
|
8924
|
+
showBody && !error && items.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-xs text-faint", children: "No scheduled work" }),
|
|
8925
|
+
showBody && !error && items.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("pb-2", hideHeader && "pt-1"), children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
8926
|
+
ScheduledWorkRow,
|
|
8927
|
+
{
|
|
8928
|
+
item,
|
|
8929
|
+
canRunNow: Boolean(adapter.runScheduledWorkNow),
|
|
8930
|
+
canCancel: Boolean(adapter.cancelScheduledWork),
|
|
8931
|
+
actionScheduleId,
|
|
8932
|
+
onOpenLastRun: handleOpenLastRun,
|
|
8933
|
+
onRunNow: handleRunNow,
|
|
8934
|
+
onCancel: handleCancel
|
|
8935
|
+
},
|
|
8936
|
+
item.id
|
|
8937
|
+
)) })
|
|
8938
|
+
] });
|
|
8939
|
+
}
|
|
8940
|
+
init_utils();
|
|
8561
8941
|
var POLL_INTERVAL_ACTIVE_MS = 4e3;
|
|
8562
8942
|
var POLL_INTERVAL_IDLE_MS = 3e4;
|
|
8563
8943
|
var DEFAULT_SESSION_LIST_LIMIT = 50;
|
|
@@ -8582,7 +8962,7 @@ function buildTree(nodes) {
|
|
|
8582
8962
|
}
|
|
8583
8963
|
function treeFingerprint(nodes) {
|
|
8584
8964
|
return nodes.map(
|
|
8585
|
-
(n) => `${n.id}:${n.parentId ?? ""}:${n.status}:${n.agentType ?? ""}:${n.messageCount ?? 0}:${n.toolCallCount ?? 0}:${n.updatedAt}`
|
|
8965
|
+
(n) => `${n.id}:${n.parentId ?? ""}:${n.status}:${n.agentType ?? ""}:${n.runKind ?? ""}:${n.title ?? ""}:${n.messageCount ?? 0}:${n.toolCallCount ?? 0}:${n.updatedAt}`
|
|
8586
8966
|
).join("|");
|
|
8587
8967
|
}
|
|
8588
8968
|
function sessionToTreeNode(session) {
|
|
@@ -8592,6 +8972,7 @@ function sessionToTreeNode(session) {
|
|
|
8592
8972
|
parentId: session.parentId ?? null,
|
|
8593
8973
|
agentId: session.agentId,
|
|
8594
8974
|
channel: session.channel,
|
|
8975
|
+
runKind: session.runKind ?? deriveRunKind(session.channel, session.config),
|
|
8595
8976
|
status: session.status,
|
|
8596
8977
|
title: session.title,
|
|
8597
8978
|
model: session.model,
|
|
@@ -8640,6 +9021,23 @@ function formatSessionTime(value) {
|
|
|
8640
9021
|
if (Number.isNaN(date.getTime())) return "";
|
|
8641
9022
|
return dateFns.formatDistanceToNow(date, { addSuffix: true });
|
|
8642
9023
|
}
|
|
9024
|
+
function deriveRunKind(channel, config) {
|
|
9025
|
+
if (channel === "scheduled" && config?.scheduled_dynamic_loop === true) {
|
|
9026
|
+
return "dynamic_loop";
|
|
9027
|
+
}
|
|
9028
|
+
if (channel === "scheduled") return "scheduled";
|
|
9029
|
+
return null;
|
|
9030
|
+
}
|
|
9031
|
+
function formatRunKind(value) {
|
|
9032
|
+
if (value === "dynamic_loop") return "Loop";
|
|
9033
|
+
if (value === "scheduled") return "Scheduled";
|
|
9034
|
+
return null;
|
|
9035
|
+
}
|
|
9036
|
+
function fallbackSessionTitle(entry) {
|
|
9037
|
+
if (entry.runKind === "dynamic_loop") return "Loop run";
|
|
9038
|
+
if (entry.runKind === "scheduled") return "Scheduled run";
|
|
9039
|
+
return "New session";
|
|
9040
|
+
}
|
|
8643
9041
|
function TreeNodeRow({
|
|
8644
9042
|
entry,
|
|
8645
9043
|
depth,
|
|
@@ -8655,9 +9053,11 @@ function TreeNodeRow({
|
|
|
8655
9053
|
const hasChildren = entry.children.length > 0;
|
|
8656
9054
|
const isActive = entry.id === activeSessionId;
|
|
8657
9055
|
const isRunning = entry.status === "active";
|
|
8658
|
-
const
|
|
8659
|
-
const title = entry.title ??
|
|
9056
|
+
const isChildSession = entry.parentId != null;
|
|
9057
|
+
const title = entry.title ?? fallbackSessionTitle(entry);
|
|
8660
9058
|
const subtitle = [
|
|
9059
|
+
formatRunKind(entry.runKind),
|
|
9060
|
+
entry.agentType,
|
|
8661
9061
|
entry.model ?? "default",
|
|
8662
9062
|
formatSessionTime(entry.updatedAt)
|
|
8663
9063
|
].filter(Boolean).join(" \xB7 ");
|
|
@@ -8695,7 +9095,7 @@ function TreeNodeRow({
|
|
|
8695
9095
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm truncate", children: title }),
|
|
8696
9096
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-faint truncate", children: subtitle })
|
|
8697
9097
|
] }),
|
|
8698
|
-
isRunning && canStop &&
|
|
9098
|
+
isRunning && canStop && isChildSession && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8699
9099
|
"button",
|
|
8700
9100
|
{
|
|
8701
9101
|
type: "button",
|
|
@@ -8704,8 +9104,8 @@ function TreeNodeRow({
|
|
|
8704
9104
|
e.stopPropagation();
|
|
8705
9105
|
onStop(entry.id);
|
|
8706
9106
|
},
|
|
8707
|
-
"aria-label": "Stop
|
|
8708
|
-
title: "Stop
|
|
9107
|
+
"aria-label": "Stop child session",
|
|
9108
|
+
title: "Stop child session",
|
|
8709
9109
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SquareIcon, { className: "w-3 h-3", fill: "currentColor" })
|
|
8710
9110
|
}
|
|
8711
9111
|
),
|
|
@@ -8909,6 +9309,7 @@ function SessionTreePanel({
|
|
|
8909
9309
|
exports.AgentChat = AgentChat;
|
|
8910
9310
|
exports.AgentChatAdapterProvider = AgentChatAdapterProvider;
|
|
8911
9311
|
exports.MessageResponse = MessageResponse;
|
|
9312
|
+
exports.ScheduledWorkPanel = ScheduledWorkPanel;
|
|
8912
9313
|
exports.SessionTreePanel = SessionTreePanel;
|
|
8913
9314
|
exports.useAgentChatAdapterContext = useAgentChatAdapterContext;
|
|
8914
9315
|
exports.useAgentChatRuntime = useAgentChatRuntime;
|