@invergent/agent-chat-react 1.5.9 → 1.6.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/README.md +13 -0
- package/dist/index.cjs +761 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -3
- package/dist/index.d.ts +81 -3
- package/dist/index.js +757 -66
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6,9 +6,9 @@ var react = require('react');
|
|
|
6
6
|
var auto = require('chart.js/auto');
|
|
7
7
|
var nextThemes = require('next-themes');
|
|
8
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
var lucideReact = require('lucide-react');
|
|
9
10
|
var classVarianceAuthority = require('class-variance-authority');
|
|
10
11
|
var radixUi = require('radix-ui');
|
|
11
|
-
var lucideReact = require('lucide-react');
|
|
12
12
|
var useStickToBottom = require('use-stick-to-bottom');
|
|
13
13
|
var cjk = require('@streamdown/cjk');
|
|
14
14
|
var code = require('@streamdown/code');
|
|
@@ -263,6 +263,117 @@ function Button({
|
|
|
263
263
|
}
|
|
264
264
|
);
|
|
265
265
|
}
|
|
266
|
+
function BrowserControlBar({
|
|
267
|
+
sessionId,
|
|
268
|
+
hasControl,
|
|
269
|
+
adapter
|
|
270
|
+
}) {
|
|
271
|
+
const [pending, setPending] = react.useState(false);
|
|
272
|
+
const [error, setError] = react.useState(null);
|
|
273
|
+
async function toggleControl() {
|
|
274
|
+
if (!sessionId || pending) return;
|
|
275
|
+
setPending(true);
|
|
276
|
+
setError(null);
|
|
277
|
+
try {
|
|
278
|
+
if (hasControl) {
|
|
279
|
+
await adapter.releaseBrowserControl(sessionId);
|
|
280
|
+
} else {
|
|
281
|
+
await adapter.acquireBrowserControl(sessionId);
|
|
282
|
+
}
|
|
283
|
+
} catch (nextError) {
|
|
284
|
+
setError(nextError.message);
|
|
285
|
+
} finally {
|
|
286
|
+
setPending(false);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-11 items-center gap-2 border-t border-line bg-card px-3 py-2", children: [
|
|
290
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
291
|
+
Button,
|
|
292
|
+
{
|
|
293
|
+
type: "button",
|
|
294
|
+
size: "xs",
|
|
295
|
+
variant: hasControl ? "secondary" : "default",
|
|
296
|
+
disabled: !sessionId || pending,
|
|
297
|
+
onClick: () => void toggleControl(),
|
|
298
|
+
children: [
|
|
299
|
+
hasControl ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcwIcon, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MousePointer2Icon, { className: "size-3", "aria-hidden": "true" }),
|
|
300
|
+
hasControl ? "Return control" : "Take control"
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
),
|
|
304
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
|
|
305
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1.5 rounded-full bg-red-500", "aria-hidden": "true" }),
|
|
306
|
+
"Live"
|
|
307
|
+
] }),
|
|
308
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error })
|
|
309
|
+
] });
|
|
310
|
+
}
|
|
311
|
+
function BrowserLiveView({ src }) {
|
|
312
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
313
|
+
"iframe",
|
|
314
|
+
{
|
|
315
|
+
"data-testid": "browser-iframe",
|
|
316
|
+
title: "Browser live view",
|
|
317
|
+
src,
|
|
318
|
+
className: "h-full w-full border-0 bg-black",
|
|
319
|
+
allow: "clipboard-read; clipboard-write"
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/components/browser/browser-status-dot.tsx
|
|
325
|
+
init_utils();
|
|
326
|
+
function BrowserStatusDot({
|
|
327
|
+
status
|
|
328
|
+
}) {
|
|
329
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
330
|
+
"span",
|
|
331
|
+
{
|
|
332
|
+
"aria-hidden": "true",
|
|
333
|
+
className: cn(
|
|
334
|
+
"inline-block size-2 shrink-0 rounded-full",
|
|
335
|
+
status === "live" && "bg-emerald-500",
|
|
336
|
+
status === "user-control" && "bg-amber-500",
|
|
337
|
+
status === "provisioning" && "animate-pulse bg-sky-500",
|
|
338
|
+
status === "closed" && "bg-muted-foreground"
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
function BrowserPane({ sessionId, state, adapter }) {
|
|
344
|
+
const liveViewUrl = react.useMemo(
|
|
345
|
+
() => adapter.browserLiveViewUrl(sessionId),
|
|
346
|
+
[adapter, sessionId]
|
|
347
|
+
);
|
|
348
|
+
const hasLiveView = state.status !== "provisioning" && state.status !== "closed";
|
|
349
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
350
|
+
"div",
|
|
351
|
+
{
|
|
352
|
+
"data-testid": "browser-pane",
|
|
353
|
+
className: "flex h-full min-h-0 flex-col bg-background",
|
|
354
|
+
children: [
|
|
355
|
+
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex min-h-10 items-center gap-2 border-b border-line bg-card px-3 text-xs text-foreground", children: [
|
|
356
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
|
|
357
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Browser" }),
|
|
358
|
+
/* @__PURE__ */ jsxRuntime.jsx(BrowserStatusDot, { status: state.status }),
|
|
359
|
+
state.controlOwner && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate text-amber-500", children: [
|
|
360
|
+
state.controlOwner,
|
|
361
|
+
" has control"
|
|
362
|
+
] })
|
|
363
|
+
] }),
|
|
364
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 bg-black", children: state.status === "provisioning" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Starting browser..." }) : state.status === "closed" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser closed." }) : /* @__PURE__ */ jsxRuntime.jsx(BrowserLiveView, { src: liveViewUrl }) }),
|
|
365
|
+
hasLiveView && /* @__PURE__ */ jsxRuntime.jsx(
|
|
366
|
+
BrowserControlBar,
|
|
367
|
+
{
|
|
368
|
+
sessionId,
|
|
369
|
+
hasControl: state.status === "user-control",
|
|
370
|
+
adapter
|
|
371
|
+
}
|
|
372
|
+
)
|
|
373
|
+
]
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
}
|
|
266
377
|
|
|
267
378
|
// src/components/ai-elements/conversation.tsx
|
|
268
379
|
init_utils();
|
|
@@ -767,6 +878,80 @@ function TimelineSeparator({
|
|
|
767
878
|
}
|
|
768
879
|
);
|
|
769
880
|
}
|
|
881
|
+
function BrowserActivityGroup({ calls }) {
|
|
882
|
+
const [open, setOpen] = react.useState(false);
|
|
883
|
+
const latest = calls[calls.length - 1];
|
|
884
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-none border border-line bg-card text-xs", children: [
|
|
885
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
886
|
+
"button",
|
|
887
|
+
{
|
|
888
|
+
type: "button",
|
|
889
|
+
onClick: () => setOpen((current) => !current),
|
|
890
|
+
className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-muted/40",
|
|
891
|
+
children: [
|
|
892
|
+
open ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDownIcon, { className: "size-3 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "size-3 shrink-0", "aria-hidden": "true" }),
|
|
893
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZapIcon, { className: "size-3 shrink-0 text-amber-500", "aria-hidden": "true" }),
|
|
894
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: "browser" }),
|
|
895
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate text-muted-foreground", children: [
|
|
896
|
+
"(",
|
|
897
|
+
calls.length,
|
|
898
|
+
" ",
|
|
899
|
+
calls.length === 1 ? "action" : "actions",
|
|
900
|
+
" - latest:",
|
|
901
|
+
" ",
|
|
902
|
+
latest ? summarise(latest) : "none",
|
|
903
|
+
")"
|
|
904
|
+
] })
|
|
905
|
+
]
|
|
906
|
+
}
|
|
907
|
+
),
|
|
908
|
+
open && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "border-t border-line py-1", children: calls.map((call) => {
|
|
909
|
+
const error = stringValue(parseJson(call.result).error);
|
|
910
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
911
|
+
"li",
|
|
912
|
+
{
|
|
913
|
+
className: "flex items-center gap-2 px-6 py-1 font-mono text-[11px]",
|
|
914
|
+
children: [
|
|
915
|
+
error && /* @__PURE__ */ jsxRuntime.jsx(
|
|
916
|
+
lucideReact.AlertCircleIcon,
|
|
917
|
+
{
|
|
918
|
+
"data-testid": `activity-error-${call.id}`,
|
|
919
|
+
className: "size-3 shrink-0 text-destructive",
|
|
920
|
+
"aria-hidden": "true"
|
|
921
|
+
}
|
|
922
|
+
),
|
|
923
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: summarise(call) }),
|
|
924
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto max-w-40 truncate text-destructive", children: error })
|
|
925
|
+
]
|
|
926
|
+
},
|
|
927
|
+
call.id
|
|
928
|
+
);
|
|
929
|
+
}) })
|
|
930
|
+
] });
|
|
931
|
+
}
|
|
932
|
+
function summarise(call) {
|
|
933
|
+
const verb = call.toolName.replace(/^browser_/, "");
|
|
934
|
+
const args = parseJson(call.args);
|
|
935
|
+
const ref = stringValue(args.ref);
|
|
936
|
+
const url = stringValue(args.url);
|
|
937
|
+
const text = stringValue(args.text);
|
|
938
|
+
if (url) return `${verb} ${url}`;
|
|
939
|
+
if (ref) return `${verb} ${ref}`;
|
|
940
|
+
if (text) return `${verb} "${text.slice(0, 24)}"`;
|
|
941
|
+
return verb;
|
|
942
|
+
}
|
|
943
|
+
function parseJson(raw) {
|
|
944
|
+
if (!raw) return {};
|
|
945
|
+
try {
|
|
946
|
+
const parsed = JSON.parse(raw);
|
|
947
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
948
|
+
} catch {
|
|
949
|
+
return {};
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
function stringValue(value) {
|
|
953
|
+
return typeof value === "string" ? value : "";
|
|
954
|
+
}
|
|
770
955
|
init_utils();
|
|
771
956
|
|
|
772
957
|
// src/components/chat/tools/shared.ts
|
|
@@ -3552,13 +3737,16 @@ function ClarifyLocked({
|
|
|
3552
3737
|
}) })
|
|
3553
3738
|
] });
|
|
3554
3739
|
}
|
|
3555
|
-
function ArtifactToolBlock({
|
|
3556
|
-
|
|
3740
|
+
function ArtifactToolBlock({
|
|
3741
|
+
tc,
|
|
3742
|
+
resolvedName
|
|
3743
|
+
}) {
|
|
3557
3744
|
const status = effectiveStatus(tc);
|
|
3558
3745
|
const label = status === "running" ? "Creating artifact\u2026" : status === "error" ? "Creating artifact\u2026" : "Created";
|
|
3746
|
+
const name = resolvedName ?? parseArgs(tc.args)?.name;
|
|
3559
3747
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-sm ", children: [
|
|
3560
3748
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: label }),
|
|
3561
|
-
|
|
3749
|
+
name && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground truncate", children: name })
|
|
3562
3750
|
] });
|
|
3563
3751
|
}
|
|
3564
3752
|
|
|
@@ -3891,7 +4079,11 @@ function DefaultToolBlock({ tc }) {
|
|
|
3891
4079
|
] })
|
|
3892
4080
|
] });
|
|
3893
4081
|
}
|
|
3894
|
-
function ToolCallBlock({
|
|
4082
|
+
function ToolCallBlock({
|
|
4083
|
+
tc,
|
|
4084
|
+
resolvedArtifactName,
|
|
4085
|
+
onFileSelect
|
|
4086
|
+
}) {
|
|
3895
4087
|
switch (tc.toolName) {
|
|
3896
4088
|
case "terminal":
|
|
3897
4089
|
return /* @__PURE__ */ jsxRuntime.jsx(TerminalToolBlock, { tc });
|
|
@@ -3928,7 +4120,7 @@ function ToolCallBlock({ tc, onFileSelect }) {
|
|
|
3928
4120
|
case "clarify":
|
|
3929
4121
|
return /* @__PURE__ */ jsxRuntime.jsx(ClarifyToolBlock, { tc });
|
|
3930
4122
|
case "create_artifact":
|
|
3931
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ArtifactToolBlock, { tc });
|
|
4123
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ArtifactToolBlock, { tc, resolvedName: resolvedArtifactName });
|
|
3932
4124
|
case "delegate_task":
|
|
3933
4125
|
return /* @__PURE__ */ jsxRuntime.jsx(DelegateToolBlock, { tc });
|
|
3934
4126
|
case "memory":
|
|
@@ -5337,6 +5529,7 @@ function ChatComposerInner({
|
|
|
5337
5529
|
onStop,
|
|
5338
5530
|
isRunning,
|
|
5339
5531
|
disabled = false,
|
|
5532
|
+
disabledReason,
|
|
5340
5533
|
tokenUsage
|
|
5341
5534
|
}) {
|
|
5342
5535
|
const { adapter } = useAgentChatAdapterContext();
|
|
@@ -5365,7 +5558,10 @@ function ChatComposerInner({
|
|
|
5365
5558
|
const slashCommands = react.useMemo(() => {
|
|
5366
5559
|
const builtin = [
|
|
5367
5560
|
{ value: "/clear", label: "/clear", description: "Clear conversation" },
|
|
5368
|
-
{ value: "/compress", label: "/compress", description: "Compress context" }
|
|
5561
|
+
{ value: "/compress", label: "/compress", description: "Compress context" },
|
|
5562
|
+
{ value: "/loop", label: "/loop", description: "Schedule recurring prompt" },
|
|
5563
|
+
{ value: "/loop list", label: "/loop list", description: "List active loops" },
|
|
5564
|
+
{ value: "/loop cancel", label: "/loop cancel", description: "Cancel a loop by ID" }
|
|
5369
5565
|
];
|
|
5370
5566
|
return [...adapterCommands, ...builtin];
|
|
5371
5567
|
}, [adapterCommands]);
|
|
@@ -5444,7 +5640,7 @@ function ChatComposerInner({
|
|
|
5444
5640
|
/* @__PURE__ */ jsxRuntime.jsx(PromptInputBody, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5445
5641
|
PromptInputTextarea,
|
|
5446
5642
|
{
|
|
5447
|
-
placeholder: disabled ? "Session disabled" : "Send a message...",
|
|
5643
|
+
placeholder: disabled ? disabledReason ?? "Session disabled" : "Send a message...",
|
|
5448
5644
|
disabled,
|
|
5449
5645
|
onKeyDown: handleKeyDown
|
|
5450
5646
|
}
|
|
@@ -6099,7 +6295,7 @@ var ErrorMessage = react.memo(function ErrorMessage2({
|
|
|
6099
6295
|
|
|
6100
6296
|
// src/components/chat/chat-thread.tsx
|
|
6101
6297
|
init_utils();
|
|
6102
|
-
function messageToEntries(msg, isLast) {
|
|
6298
|
+
function messageToEntries(msg, isLast, artifactFallbacks) {
|
|
6103
6299
|
if (msg.role === "system") {
|
|
6104
6300
|
if (msg.systemKind === "skill_invoked") {
|
|
6105
6301
|
return [{
|
|
@@ -6141,6 +6337,18 @@ function messageToEntries(msg, isLast) {
|
|
|
6141
6337
|
}
|
|
6142
6338
|
if (hasToolCalls) {
|
|
6143
6339
|
for (const tc of msg.toolCalls) {
|
|
6340
|
+
if (tc.toolName === "create_artifact") {
|
|
6341
|
+
const resolvedName = parseArgs(tc.args)?.name ?? artifactFallbacks[tc.id];
|
|
6342
|
+
const status = effectiveStatus(tc);
|
|
6343
|
+
if (!resolvedName && status !== "error") continue;
|
|
6344
|
+
entries.push({
|
|
6345
|
+
kind: "tool",
|
|
6346
|
+
key: tc.id,
|
|
6347
|
+
tc,
|
|
6348
|
+
resolvedArtifactName: resolvedName
|
|
6349
|
+
});
|
|
6350
|
+
continue;
|
|
6351
|
+
}
|
|
6144
6352
|
entries.push({ kind: "tool", key: tc.id, tc });
|
|
6145
6353
|
}
|
|
6146
6354
|
}
|
|
@@ -6155,6 +6363,32 @@ function messageToEntries(msg, isLast) {
|
|
|
6155
6363
|
}
|
|
6156
6364
|
return entries;
|
|
6157
6365
|
}
|
|
6366
|
+
function groupBrowserActivityEntries(entries) {
|
|
6367
|
+
const grouped = [];
|
|
6368
|
+
let buffer = [];
|
|
6369
|
+
const flush = () => {
|
|
6370
|
+
if (buffer.length === 0) return;
|
|
6371
|
+
grouped.push({
|
|
6372
|
+
kind: "browser_activity",
|
|
6373
|
+
key: `browser-activity-${buffer[0]?.id}-${buffer[buffer.length - 1]?.id}`,
|
|
6374
|
+
calls: buffer
|
|
6375
|
+
});
|
|
6376
|
+
buffer = [];
|
|
6377
|
+
};
|
|
6378
|
+
for (const entry of entries) {
|
|
6379
|
+
if (entry.kind === "tool" && isBrowserToolCall(entry.tc)) {
|
|
6380
|
+
buffer.push(entry.tc);
|
|
6381
|
+
continue;
|
|
6382
|
+
}
|
|
6383
|
+
flush();
|
|
6384
|
+
grouped.push(entry);
|
|
6385
|
+
}
|
|
6386
|
+
flush();
|
|
6387
|
+
return grouped;
|
|
6388
|
+
}
|
|
6389
|
+
function isBrowserToolCall(call) {
|
|
6390
|
+
return call.toolName.startsWith("browser_");
|
|
6391
|
+
}
|
|
6158
6392
|
function groupMessages(messages) {
|
|
6159
6393
|
const groups = [];
|
|
6160
6394
|
let pendingSystem = [];
|
|
@@ -6299,11 +6533,27 @@ function TimelineEntryItem({
|
|
|
6299
6533
|
)
|
|
6300
6534
|
] }),
|
|
6301
6535
|
/* @__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(
|
|
6536
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6537
|
+
ToolCallBlock,
|
|
6538
|
+
{
|
|
6539
|
+
tc: entry.tc,
|
|
6540
|
+
resolvedArtifactName: entry.resolvedArtifactName,
|
|
6541
|
+
onFileSelect
|
|
6542
|
+
}
|
|
6543
|
+
),
|
|
6303
6544
|
failureSummary ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-full truncate text-xs text-destructive", title: failureSummary, children: failureSummary }) : null
|
|
6304
6545
|
] }) })
|
|
6305
6546
|
] });
|
|
6306
6547
|
}
|
|
6548
|
+
if (entry.kind === "browser_activity") {
|
|
6549
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
|
|
6550
|
+
/* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
|
|
6551
|
+
/* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
|
|
6552
|
+
/* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-amber-500" })
|
|
6553
|
+
] }),
|
|
6554
|
+
/* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx(BrowserActivityGroup, { calls: entry.calls }) })
|
|
6555
|
+
] });
|
|
6556
|
+
}
|
|
6307
6557
|
if (entry.kind === "text") {
|
|
6308
6558
|
return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
|
|
6309
6559
|
/* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
|
|
@@ -6361,14 +6611,16 @@ function AssistantGroup({
|
|
|
6361
6611
|
totalMessages,
|
|
6362
6612
|
isRunning,
|
|
6363
6613
|
sessionId,
|
|
6614
|
+
artifactFallbacks,
|
|
6364
6615
|
onFileSelect,
|
|
6365
6616
|
onRetry
|
|
6366
6617
|
}) {
|
|
6367
|
-
|
|
6618
|
+
let entries = [];
|
|
6368
6619
|
for (let i = 0; i < messages.length; i++) {
|
|
6369
6620
|
const isLast = i === messages.length - 1 && lastGlobalIndex === totalMessages - 1;
|
|
6370
|
-
entries.push(...messageToEntries(messages[i], isLast));
|
|
6621
|
+
entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
|
|
6371
6622
|
}
|
|
6623
|
+
entries = groupBrowserActivityEntries(entries);
|
|
6372
6624
|
const isTailGroup = lastGlobalIndex === totalMessages - 1;
|
|
6373
6625
|
const lastEntry = entries[entries.length - 1];
|
|
6374
6626
|
const hasRunningTool = entries.some(
|
|
@@ -6434,11 +6686,34 @@ function ChatThread({
|
|
|
6434
6686
|
onStop,
|
|
6435
6687
|
onFileSelect,
|
|
6436
6688
|
disabled = false,
|
|
6689
|
+
disabledReason,
|
|
6437
6690
|
tokenUsage,
|
|
6438
6691
|
retryIndicator,
|
|
6439
6692
|
onRetry
|
|
6440
6693
|
}) {
|
|
6441
6694
|
const groups = react.useMemo(() => groupMessages(messages), [messages]);
|
|
6695
|
+
const artifactFallbacks = react.useMemo(() => {
|
|
6696
|
+
const fallbacks = {};
|
|
6697
|
+
const createArtifactToolCallIds = [];
|
|
6698
|
+
let artifactIdx = 0;
|
|
6699
|
+
for (const msg of messages) {
|
|
6700
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
6701
|
+
for (const tc of msg.toolCalls) {
|
|
6702
|
+
if (tc.toolName === "create_artifact" && !tc.cancelled && tc.status !== "error") {
|
|
6703
|
+
createArtifactToolCallIds.push(tc.id);
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
6706
|
+
} else if (msg.role === "system" && msg.systemKind === "artifact") {
|
|
6707
|
+
const tcId = createArtifactToolCallIds[artifactIdx];
|
|
6708
|
+
if (tcId) {
|
|
6709
|
+
const { name } = unpackArtifactMeta(msg.systemMeta, msg.content);
|
|
6710
|
+
if (name) fallbacks[tcId] = name;
|
|
6711
|
+
}
|
|
6712
|
+
artifactIdx += 1;
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
6715
|
+
return fallbacks;
|
|
6716
|
+
}, [messages]);
|
|
6442
6717
|
const activeFailureId = react.useMemo(() => {
|
|
6443
6718
|
if (messages.length === 0) return null;
|
|
6444
6719
|
const tail = messages[messages.length - 1];
|
|
@@ -6503,6 +6778,7 @@ function ChatThread({
|
|
|
6503
6778
|
totalMessages: messages.length,
|
|
6504
6779
|
isRunning,
|
|
6505
6780
|
sessionId,
|
|
6781
|
+
artifactFallbacks,
|
|
6506
6782
|
onFileSelect,
|
|
6507
6783
|
onRetry: groupRetry
|
|
6508
6784
|
},
|
|
@@ -6515,13 +6791,21 @@ function ChatThread({
|
|
|
6515
6791
|
] }),
|
|
6516
6792
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-4xl px-6 pb-5 pt-3", children: [
|
|
6517
6793
|
retryIndicator && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(RetryBanner, { indicator: retryIndicator }) }),
|
|
6518
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6794
|
+
disabled && disabledReason ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6795
|
+
"div",
|
|
6796
|
+
{
|
|
6797
|
+
className: "rounded border border-line bg-muted/40 px-3 py-2 text-sm text-muted-foreground",
|
|
6798
|
+
role: "status",
|
|
6799
|
+
children: disabledReason
|
|
6800
|
+
}
|
|
6801
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
6519
6802
|
ChatComposer,
|
|
6520
6803
|
{
|
|
6521
6804
|
onSend,
|
|
6522
6805
|
onStop,
|
|
6523
6806
|
isRunning,
|
|
6524
6807
|
disabled,
|
|
6808
|
+
disabledReason,
|
|
6525
6809
|
tokenUsage
|
|
6526
6810
|
}
|
|
6527
6811
|
)
|
|
@@ -7359,7 +7643,7 @@ function WorkspacePanel({
|
|
|
7359
7643
|
);
|
|
7360
7644
|
const handleDelete = react.useCallback(
|
|
7361
7645
|
async (path) => {
|
|
7362
|
-
if (!sessionId) return;
|
|
7646
|
+
if (disabled || !sessionId) return;
|
|
7363
7647
|
try {
|
|
7364
7648
|
await adapter.deleteWorkspaceFile({ sessionId, path });
|
|
7365
7649
|
if (selectedPath === path) {
|
|
@@ -7375,7 +7659,7 @@ function WorkspacePanel({
|
|
|
7375
7659
|
setDeleteTarget(null);
|
|
7376
7660
|
}
|
|
7377
7661
|
},
|
|
7378
|
-
[adapter, fetchTree, onSelectedPathChange, selectedPath, sessionId]
|
|
7662
|
+
[adapter, disabled, fetchTree, onSelectedPathChange, selectedPath, sessionId]
|
|
7379
7663
|
);
|
|
7380
7664
|
const onResizeStart = react.useCallback(
|
|
7381
7665
|
(event) => {
|
|
@@ -7413,6 +7697,7 @@ function WorkspacePanel({
|
|
|
7413
7697
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
7414
7698
|
"aside",
|
|
7415
7699
|
{
|
|
7700
|
+
"data-testid": "workspace-panel",
|
|
7416
7701
|
role: "button",
|
|
7417
7702
|
tabIndex: 0,
|
|
7418
7703
|
className: "relative z-10 flex min-h-0 w-10 shrink-0 cursor-pointer items-center justify-center border-l border-muted-foreground/20 bg-card text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30",
|
|
@@ -7431,6 +7716,7 @@ function WorkspacePanel({
|
|
|
7431
7716
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
7432
7717
|
"aside",
|
|
7433
7718
|
{
|
|
7719
|
+
"data-testid": "workspace-panel",
|
|
7434
7720
|
className: "relative z-10 flex min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
|
|
7435
7721
|
style: { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
|
|
7436
7722
|
children: [
|
|
@@ -7458,7 +7744,7 @@ function WorkspacePanel({
|
|
|
7458
7744
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpenIcon, { className: "size-4 shrink-0 text-amber-500" }),
|
|
7459
7745
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
7460
7746
|
/* @__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" })
|
|
7747
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs text-faint", children: disabled ? "Workspace - Read-only" : "Workspace" })
|
|
7462
7748
|
] }),
|
|
7463
7749
|
/* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
7464
7750
|
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -7569,7 +7855,7 @@ function WorkspacePanel({
|
|
|
7569
7855
|
sessionId,
|
|
7570
7856
|
path: file.path
|
|
7571
7857
|
}) : null,
|
|
7572
|
-
onDelete: file ? () => setDeleteTarget(file.path) : null,
|
|
7858
|
+
onDelete: file && !disabled ? () => setDeleteTarget(file.path) : null,
|
|
7573
7859
|
onClose: () => {
|
|
7574
7860
|
onSelectedPathChange(null);
|
|
7575
7861
|
setFile(null);
|
|
@@ -7633,6 +7919,13 @@ function ConfirmDialog({
|
|
|
7633
7919
|
);
|
|
7634
7920
|
}
|
|
7635
7921
|
|
|
7922
|
+
// src/lib/sessions.ts
|
|
7923
|
+
function isScheduledRunSession(session) {
|
|
7924
|
+
return Boolean(
|
|
7925
|
+
session?.channel === "scheduled" || session?.config?.scheduled_session_id
|
|
7926
|
+
);
|
|
7927
|
+
}
|
|
7928
|
+
|
|
7636
7929
|
// src/runtime/events.ts
|
|
7637
7930
|
var WORKSPACE_MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
7638
7931
|
"terminal",
|
|
@@ -7665,6 +7958,10 @@ var AGENT_CHAT_LISTENED_EVENTS = [
|
|
|
7665
7958
|
"expert.override",
|
|
7666
7959
|
"artifact.created",
|
|
7667
7960
|
"artifact.updated",
|
|
7961
|
+
"browser.provisioned",
|
|
7962
|
+
"browser.destroyed",
|
|
7963
|
+
"browser.control_granted",
|
|
7964
|
+
"browser.control_returned",
|
|
7668
7965
|
"clarify.response"
|
|
7669
7966
|
];
|
|
7670
7967
|
|
|
@@ -7689,7 +7986,8 @@ function createInitialAgentChatState(options = {}) {
|
|
|
7689
7986
|
sessionDone: false,
|
|
7690
7987
|
hadDeltas: false,
|
|
7691
7988
|
terminal: false,
|
|
7692
|
-
workspaceRefreshKey: 0
|
|
7989
|
+
workspaceRefreshKey: 0,
|
|
7990
|
+
browser: null
|
|
7693
7991
|
};
|
|
7694
7992
|
}
|
|
7695
7993
|
function applyAgentChatEvent(state, event) {
|
|
@@ -7713,7 +8011,7 @@ function applyAgentChatEvent(state, event) {
|
|
|
7713
8011
|
{
|
|
7714
8012
|
id: `evt-${event.eventId}`,
|
|
7715
8013
|
role: "system",
|
|
7716
|
-
content:
|
|
8014
|
+
content: stringValue2(event.data.skill),
|
|
7717
8015
|
createdAt: /* @__PURE__ */ new Date(),
|
|
7718
8016
|
status: "complete",
|
|
7719
8017
|
systemKind: "skill_invoked",
|
|
@@ -7730,7 +8028,7 @@ function applyAgentChatEvent(state, event) {
|
|
|
7730
8028
|
{
|
|
7731
8029
|
id: `evt-${event.eventId}`,
|
|
7732
8030
|
role: "system",
|
|
7733
|
-
content:
|
|
8031
|
+
content: stringValue2(event.data.name),
|
|
7734
8032
|
createdAt: /* @__PURE__ */ new Date(),
|
|
7735
8033
|
status: "complete",
|
|
7736
8034
|
systemKind: "artifact",
|
|
@@ -7743,6 +8041,23 @@ function applyAgentChatEvent(state, event) {
|
|
|
7743
8041
|
}
|
|
7744
8042
|
}
|
|
7745
8043
|
]);
|
|
8044
|
+
case "browser.provisioned":
|
|
8045
|
+
return applyBrowserEvent(nextState, event, {
|
|
8046
|
+
status: "live",
|
|
8047
|
+
controlOwner: null
|
|
8048
|
+
});
|
|
8049
|
+
case "browser.control_granted":
|
|
8050
|
+
return applyBrowserEvent(nextState, event, {
|
|
8051
|
+
status: "user-control",
|
|
8052
|
+
controlOwner: stringValue2(event.data.owner_user_id) || null
|
|
8053
|
+
});
|
|
8054
|
+
case "browser.control_returned":
|
|
8055
|
+
return applyBrowserEvent(nextState, event, {
|
|
8056
|
+
status: "live",
|
|
8057
|
+
controlOwner: null
|
|
8058
|
+
});
|
|
8059
|
+
case "browser.destroyed":
|
|
8060
|
+
return applyBrowserEvent(nextState, event, null);
|
|
7746
8061
|
case "llm.delta":
|
|
7747
8062
|
return applyLlmDelta(nextState, event);
|
|
7748
8063
|
case "llm.response":
|
|
@@ -7752,7 +8067,7 @@ function applyAgentChatEvent(state, event) {
|
|
|
7752
8067
|
case "tool.call":
|
|
7753
8068
|
return applyToolCall(nextState, event);
|
|
7754
8069
|
case "tool.result": {
|
|
7755
|
-
const toolCallId =
|
|
8070
|
+
const toolCallId = stringValue2(event.data.tool_call_id);
|
|
7756
8071
|
const toolName = findToolNameById(nextState.messages, toolCallId);
|
|
7757
8072
|
const messages = applyToolResult(nextState.messages, event.data);
|
|
7758
8073
|
const mutatesWorkspace = toolName !== null && WORKSPACE_MUTATING_TOOLS.has(toolName);
|
|
@@ -7822,8 +8137,8 @@ function applyRetryIndicator(state, event) {
|
|
|
7822
8137
|
case "session.done":
|
|
7823
8138
|
return { ...state, retryIndicator: null };
|
|
7824
8139
|
case "harness.crash": {
|
|
7825
|
-
const title =
|
|
7826
|
-
const detail =
|
|
8140
|
+
const title = stringValue2(event.data.error_title) || "A transient error occurred, retrying...";
|
|
8141
|
+
const detail = stringValue2(event.data.error_detail) || stringValue2(event.data.error);
|
|
7827
8142
|
return {
|
|
7828
8143
|
...state,
|
|
7829
8144
|
retryIndicator: {
|
|
@@ -7838,7 +8153,7 @@ function applyRetryIndicator(state, event) {
|
|
|
7838
8153
|
}
|
|
7839
8154
|
}
|
|
7840
8155
|
function applyUserMessage(messages, eventId, data) {
|
|
7841
|
-
const content =
|
|
8156
|
+
const content = stringValue2(data.content);
|
|
7842
8157
|
const next = [...messages];
|
|
7843
8158
|
const localIdx = next.findIndex(
|
|
7844
8159
|
(m) => m.role === "user" && m.id.startsWith("local-") && m.content === content
|
|
@@ -7856,9 +8171,47 @@ function applyUserMessage(messages, eventId, data) {
|
|
|
7856
8171
|
});
|
|
7857
8172
|
return next;
|
|
7858
8173
|
}
|
|
8174
|
+
function applyBrowserEvent(state, event, browser) {
|
|
8175
|
+
return withMessages(
|
|
8176
|
+
{ ...state, browser },
|
|
8177
|
+
[...state.messages, browserMarker(event)]
|
|
8178
|
+
);
|
|
8179
|
+
}
|
|
8180
|
+
function browserMarker(event) {
|
|
8181
|
+
const labels = {
|
|
8182
|
+
"browser.provisioned": {
|
|
8183
|
+
content: "Browser ready.",
|
|
8184
|
+
warning: false
|
|
8185
|
+
},
|
|
8186
|
+
"browser.control_granted": {
|
|
8187
|
+
content: "A user took control of the browser.",
|
|
8188
|
+
warning: true
|
|
8189
|
+
},
|
|
8190
|
+
"browser.control_returned": {
|
|
8191
|
+
content: "Browser control returned to the agent.",
|
|
8192
|
+
warning: false
|
|
8193
|
+
},
|
|
8194
|
+
"browser.destroyed": {
|
|
8195
|
+
content: "Browser closed.",
|
|
8196
|
+
warning: false
|
|
8197
|
+
}
|
|
8198
|
+
};
|
|
8199
|
+
const label = labels[event.type] ?? {
|
|
8200
|
+
content: "Browser updated.",
|
|
8201
|
+
warning: false
|
|
8202
|
+
};
|
|
8203
|
+
return {
|
|
8204
|
+
id: `browser-marker-${event.eventId}`,
|
|
8205
|
+
role: "system",
|
|
8206
|
+
content: label.content,
|
|
8207
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
8208
|
+
status: "complete",
|
|
8209
|
+
systemKind: label.warning ? "browser_marker_warning" : "browser_marker"
|
|
8210
|
+
};
|
|
8211
|
+
}
|
|
7859
8212
|
function applyLlmDelta(state, event) {
|
|
7860
|
-
const deltaContent =
|
|
7861
|
-
const deltaReasoning =
|
|
8213
|
+
const deltaContent = stringValue2(event.data.content);
|
|
8214
|
+
const deltaReasoning = stringValue2(event.data.reasoning);
|
|
7862
8215
|
const messages = [...state.messages];
|
|
7863
8216
|
const lastIdx = findLastAssistantIndex(messages);
|
|
7864
8217
|
const lastMsg = lastIdx >= 0 ? messages[lastIdx] : null;
|
|
@@ -7895,7 +8248,7 @@ function applyLlmDelta(state, event) {
|
|
|
7895
8248
|
function applyLlmResponse(state, event) {
|
|
7896
8249
|
const messages = [...state.messages];
|
|
7897
8250
|
const message = objectValue(event.data.message);
|
|
7898
|
-
const responseContent =
|
|
8251
|
+
const responseContent = stringValue2(message?.content) || stringValue2(event.data.content);
|
|
7899
8252
|
const toolCalls = Array.isArray(message?.tool_calls) ? message?.tool_calls : [];
|
|
7900
8253
|
const hasToolCalls = toolCalls.length > 0;
|
|
7901
8254
|
const idx = findLastAssistantIndex(messages);
|
|
@@ -7953,12 +8306,12 @@ function applyLlmResponse(state, event) {
|
|
|
7953
8306
|
cachedInputTokens: numberValue(event.data.cache_read_tokens),
|
|
7954
8307
|
totalTokens: inputTokens + outputTokens,
|
|
7955
8308
|
contextWindow: numberValue(event.data.context_window),
|
|
7956
|
-
model:
|
|
8309
|
+
model: stringValue2(event.data.model)
|
|
7957
8310
|
}
|
|
7958
8311
|
};
|
|
7959
8312
|
}
|
|
7960
8313
|
function applyLlmThinking(state, event) {
|
|
7961
|
-
const reasoningText =
|
|
8314
|
+
const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
|
|
7962
8315
|
const messages = [...state.messages];
|
|
7963
8316
|
const idx = findLastAssistantIndex(messages);
|
|
7964
8317
|
const prev = idx >= 0 ? messages[idx] : null;
|
|
@@ -8006,11 +8359,11 @@ function applyToolCall(state, event) {
|
|
|
8006
8359
|
messages[assistantIdx] = assistant;
|
|
8007
8360
|
}
|
|
8008
8361
|
const existingCalls = assistant.toolCalls ?? [];
|
|
8009
|
-
const toolCallId =
|
|
8362
|
+
const toolCallId = stringValue2(event.data.tool_call_id) || `tc-${event.eventId}`;
|
|
8010
8363
|
if (!existingCalls.some((tc) => tc.id === toolCallId)) {
|
|
8011
8364
|
const entry = {
|
|
8012
8365
|
id: toolCallId,
|
|
8013
|
-
toolName:
|
|
8366
|
+
toolName: stringValue2(event.data.name) || stringValue2(event.data.tool_name) || "unknown",
|
|
8014
8367
|
args: typeof event.data.arguments === "string" ? event.data.arguments : JSON.stringify(event.data.arguments ?? {}),
|
|
8015
8368
|
status: "running"
|
|
8016
8369
|
};
|
|
@@ -8026,7 +8379,7 @@ function applyToolCall(state, event) {
|
|
|
8026
8379
|
};
|
|
8027
8380
|
}
|
|
8028
8381
|
function applyToolResult(messages, data) {
|
|
8029
|
-
const targetId =
|
|
8382
|
+
const targetId = stringValue2(data.tool_call_id);
|
|
8030
8383
|
let matchIdx = -1;
|
|
8031
8384
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
8032
8385
|
if (messages[i]?.role === "assistant" && messages[i]?.toolCalls?.some((tc) => tc.id === targetId)) {
|
|
@@ -8095,8 +8448,8 @@ function buildErrorInfo(event) {
|
|
|
8095
8448
|
}
|
|
8096
8449
|
return {
|
|
8097
8450
|
category: event.data.error_category,
|
|
8098
|
-
title:
|
|
8099
|
-
detail:
|
|
8451
|
+
title: stringValue2(event.data.error_title) || "The session failed due to an internal error.",
|
|
8452
|
+
detail: stringValue2(event.data.error_detail) || stringValue2(event.data.error),
|
|
8100
8453
|
retryable: Boolean(event.data.retryable)
|
|
8101
8454
|
};
|
|
8102
8455
|
}
|
|
@@ -8140,7 +8493,7 @@ function applyExpertFeedback(messages, type, data) {
|
|
|
8140
8493
|
return messages;
|
|
8141
8494
|
}
|
|
8142
8495
|
function applyClarifyResponse(messages, data) {
|
|
8143
|
-
const targetToolId =
|
|
8496
|
+
const targetToolId = stringValue2(data.tool_call_id);
|
|
8144
8497
|
const responses = Array.isArray(data.responses) ? data.responses : void 0;
|
|
8145
8498
|
if (!targetToolId || !responses) return messages;
|
|
8146
8499
|
const next = [...messages];
|
|
@@ -8155,8 +8508,8 @@ function applyClarifyResponse(messages, data) {
|
|
|
8155
8508
|
clarifyAnswers: responses.map((response) => {
|
|
8156
8509
|
const row = objectValue(response) ?? {};
|
|
8157
8510
|
return {
|
|
8158
|
-
question:
|
|
8159
|
-
answer:
|
|
8511
|
+
question: stringValue2(row.question),
|
|
8512
|
+
answer: stringValue2(row.answer),
|
|
8160
8513
|
is_other: Boolean(row.is_other)
|
|
8161
8514
|
};
|
|
8162
8515
|
})
|
|
@@ -8176,7 +8529,7 @@ function applyPolicyDenied(state, data) {
|
|
|
8176
8529
|
...policyMsg,
|
|
8177
8530
|
content: `${policyMsg.content}
|
|
8178
8531
|
|
|
8179
|
-
**Policy denied**: ${
|
|
8532
|
+
**Policy denied**: ${stringValue2(data.reason) || "Action blocked by governance policy."}`,
|
|
8180
8533
|
status: "error"
|
|
8181
8534
|
};
|
|
8182
8535
|
}
|
|
@@ -8228,7 +8581,7 @@ function objectValue(value) {
|
|
|
8228
8581
|
}
|
|
8229
8582
|
return void 0;
|
|
8230
8583
|
}
|
|
8231
|
-
function
|
|
8584
|
+
function stringValue2(value) {
|
|
8232
8585
|
return typeof value === "string" ? value : "";
|
|
8233
8586
|
}
|
|
8234
8587
|
function numberValue(value) {
|
|
@@ -8245,6 +8598,7 @@ function useAgentChatRuntime({
|
|
|
8245
8598
|
const [state, setState] = react.useState(
|
|
8246
8599
|
() => createInitialAgentChatState({ isLoadingHistory: Boolean(sessionId) })
|
|
8247
8600
|
);
|
|
8601
|
+
const [session, setSession] = react.useState(null);
|
|
8248
8602
|
const stateRef = react.useRef(state);
|
|
8249
8603
|
const streamRef = react.useRef(null);
|
|
8250
8604
|
const reconnectTimerRef = react.useRef(null);
|
|
@@ -8269,6 +8623,7 @@ function useAgentChatRuntime({
|
|
|
8269
8623
|
clearReconnectTimer();
|
|
8270
8624
|
closeStream();
|
|
8271
8625
|
if (!sessionId) {
|
|
8626
|
+
setSession(null);
|
|
8272
8627
|
setState(createInitialAgentChatState());
|
|
8273
8628
|
return;
|
|
8274
8629
|
}
|
|
@@ -8316,17 +8671,19 @@ function useAgentChatRuntime({
|
|
|
8316
8671
|
isLoadingHistory: true
|
|
8317
8672
|
});
|
|
8318
8673
|
stateRef.current = initialState;
|
|
8674
|
+
setSession(null);
|
|
8319
8675
|
setState(initialState);
|
|
8320
8676
|
connect(0);
|
|
8321
|
-
adapter.getSession({ sessionId }).then((
|
|
8677
|
+
adapter.getSession({ sessionId }).then((loadedSession) => {
|
|
8322
8678
|
if (cancelled) return;
|
|
8323
|
-
|
|
8679
|
+
setSession(loadedSession);
|
|
8680
|
+
if (loadedSession.messageCount === 0) {
|
|
8324
8681
|
setState((prev) => ({
|
|
8325
8682
|
...prev,
|
|
8326
8683
|
isLoadingHistory: false
|
|
8327
8684
|
}));
|
|
8328
8685
|
}
|
|
8329
|
-
if (isTerminalStatus(
|
|
8686
|
+
if (isTerminalStatus(loadedSession.status)) {
|
|
8330
8687
|
setState((prev) => ({
|
|
8331
8688
|
...prev,
|
|
8332
8689
|
terminal: true,
|
|
@@ -8415,9 +8772,9 @@ function useAgentChatRuntime({
|
|
|
8415
8772
|
markSending(content, images);
|
|
8416
8773
|
if (!sessionId) {
|
|
8417
8774
|
try {
|
|
8418
|
-
const
|
|
8419
|
-
onSessionChange?.(
|
|
8420
|
-
await adapter.sendMessage({ sessionId:
|
|
8775
|
+
const session2 = await adapter.createSession({ agentId });
|
|
8776
|
+
onSessionChange?.(session2.id);
|
|
8777
|
+
await adapter.sendMessage({ sessionId: session2.id, content, images });
|
|
8421
8778
|
} catch (error) {
|
|
8422
8779
|
markSendError(error instanceof Error ? error.message : "send failed");
|
|
8423
8780
|
throw error;
|
|
@@ -8458,6 +8815,8 @@ function useAgentChatRuntime({
|
|
|
8458
8815
|
}
|
|
8459
8816
|
}, [adapter, sessionId]);
|
|
8460
8817
|
return {
|
|
8818
|
+
state,
|
|
8819
|
+
session,
|
|
8461
8820
|
messages: state.messages,
|
|
8462
8821
|
isRunning: state.isRunning,
|
|
8463
8822
|
isLoadingHistory: state.isLoadingHistory,
|
|
@@ -8505,6 +8864,10 @@ function AgentChat({
|
|
|
8505
8864
|
sessionId,
|
|
8506
8865
|
onSessionChange
|
|
8507
8866
|
});
|
|
8867
|
+
const isReadOnlySession = isScheduledRunSession(runtime.session);
|
|
8868
|
+
const effectiveDisabled = disabled || isReadOnlySession;
|
|
8869
|
+
const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
|
|
8870
|
+
const browserState = runtime.state.browser;
|
|
8508
8871
|
react.useEffect(() => {
|
|
8509
8872
|
onMessagesChange?.(runtime.messages);
|
|
8510
8873
|
}, [onMessagesChange, runtime.messages]);
|
|
@@ -8535,29 +8898,341 @@ function AgentChat({
|
|
|
8535
8898
|
onStop: () => void runtime.stop(),
|
|
8536
8899
|
onRetry: runtime.retry,
|
|
8537
8900
|
onFileSelect: handleFileSelect,
|
|
8538
|
-
disabled,
|
|
8901
|
+
disabled: effectiveDisabled,
|
|
8902
|
+
disabledReason,
|
|
8539
8903
|
tokenUsage: runtime.tokenUsage,
|
|
8540
8904
|
retryIndicator: runtime.retryIndicator
|
|
8541
8905
|
}
|
|
8542
8906
|
) }),
|
|
8543
|
-
/* @__PURE__ */ jsxRuntime.
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8907
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": "right-stack", className: "flex min-h-0 shrink-0 flex-col", children: [
|
|
8908
|
+
browserState !== null && sessionId && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 border-b border-line", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
8909
|
+
BrowserPane,
|
|
8910
|
+
{
|
|
8911
|
+
sessionId,
|
|
8912
|
+
state: browserState,
|
|
8913
|
+
adapter
|
|
8914
|
+
}
|
|
8915
|
+
) }),
|
|
8916
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8917
|
+
WorkspacePanel,
|
|
8918
|
+
{
|
|
8919
|
+
adapter,
|
|
8920
|
+
sessionId,
|
|
8921
|
+
selectedPath: workspacePath,
|
|
8922
|
+
onSelectedPathChange: setWorkspacePath,
|
|
8923
|
+
collapsed: workspaceCollapsed,
|
|
8924
|
+
onCollapsedChange: setWorkspaceCollapsed,
|
|
8925
|
+
refreshSignal: runtime.workspaceRefreshKey,
|
|
8926
|
+
disabled: effectiveDisabled
|
|
8927
|
+
}
|
|
8928
|
+
)
|
|
8929
|
+
] })
|
|
8556
8930
|
] }) })
|
|
8557
8931
|
}
|
|
8558
8932
|
);
|
|
8559
8933
|
}
|
|
8560
8934
|
init_utils();
|
|
8935
|
+
var DEFAULT_LIMIT = 50;
|
|
8936
|
+
var DEFAULT_POLL_INTERVAL_MS = 3e4;
|
|
8937
|
+
function fingerprint(items) {
|
|
8938
|
+
return items.map(
|
|
8939
|
+
(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}`
|
|
8940
|
+
).join("|");
|
|
8941
|
+
}
|
|
8942
|
+
function formatKind(value) {
|
|
8943
|
+
if (value === "dynamic_loop") return "Dynamic loop";
|
|
8944
|
+
if (value === "cron") return "Cron";
|
|
8945
|
+
if (value === "one_shot") return "One-shot";
|
|
8946
|
+
if (value === "scheduled") return "Scheduled";
|
|
8947
|
+
if (!value) return "Scheduled";
|
|
8948
|
+
return value.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
8949
|
+
}
|
|
8950
|
+
function formatRelative(value, label) {
|
|
8951
|
+
if (!value) return null;
|
|
8952
|
+
const date = new Date(value);
|
|
8953
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
8954
|
+
return `${label} ${dateFns.formatDistanceToNow(date, { addSuffix: true })}`;
|
|
8955
|
+
}
|
|
8956
|
+
function formatRuns(count) {
|
|
8957
|
+
return `${count} ${count === 1 ? "run" : "runs"}`;
|
|
8958
|
+
}
|
|
8959
|
+
function scheduleTitle(item) {
|
|
8960
|
+
const title = item.name?.trim();
|
|
8961
|
+
if (title) return title;
|
|
8962
|
+
return item.prompt;
|
|
8963
|
+
}
|
|
8964
|
+
function canRunScheduleNow(item) {
|
|
8965
|
+
return item.repeatLimit === 1;
|
|
8966
|
+
}
|
|
8967
|
+
function scheduleDisplay(item) {
|
|
8968
|
+
if (item.kind === "dynamic_loop") return null;
|
|
8969
|
+
return item.scheduleDisplay;
|
|
8970
|
+
}
|
|
8971
|
+
function sortScheduledWork(items) {
|
|
8972
|
+
return [...items].sort((a, b) => {
|
|
8973
|
+
if (a.status === "active" && b.status !== "active") return -1;
|
|
8974
|
+
if (a.status !== "active" && b.status === "active") return 1;
|
|
8975
|
+
const aNext = a.nextRunAt ?? "";
|
|
8976
|
+
const bNext = b.nextRunAt ?? "";
|
|
8977
|
+
if (aNext && bNext && aNext !== bNext) return aNext < bNext ? -1 : 1;
|
|
8978
|
+
if (aNext && !bNext) return -1;
|
|
8979
|
+
if (!aNext && bNext) return 1;
|
|
8980
|
+
return (a.updatedAt ?? "") > (b.updatedAt ?? "") ? -1 : 1;
|
|
8981
|
+
});
|
|
8982
|
+
}
|
|
8983
|
+
function ScheduledWorkRow({
|
|
8984
|
+
item,
|
|
8985
|
+
canRunNow,
|
|
8986
|
+
canCancel,
|
|
8987
|
+
actionScheduleId,
|
|
8988
|
+
onOpenLastRun,
|
|
8989
|
+
onRunNow,
|
|
8990
|
+
onCancel
|
|
8991
|
+
}) {
|
|
8992
|
+
const isActive = item.status === "active";
|
|
8993
|
+
const disabled = actionScheduleId === item.id;
|
|
8994
|
+
const title = scheduleTitle(item);
|
|
8995
|
+
const statusText = item.status === "active" ? null : item.status;
|
|
8996
|
+
const showRunNow = canRunNow && isActive && canRunScheduleNow(item);
|
|
8997
|
+
const lastSessionId = item.lastSessionId ?? null;
|
|
8998
|
+
const meta = [
|
|
8999
|
+
scheduleDisplay(item),
|
|
9000
|
+
formatRelative(item.nextRunAt, "Next"),
|
|
9001
|
+
formatRelative(item.lastRunAt, "Last"),
|
|
9002
|
+
formatRuns(item.runCount),
|
|
9003
|
+
item.expiresAt ? formatRelative(item.expiresAt, "Expires") : null
|
|
9004
|
+
].filter(Boolean);
|
|
9005
|
+
const openLastRun = () => {
|
|
9006
|
+
if (lastSessionId) onOpenLastRun(lastSessionId);
|
|
9007
|
+
};
|
|
9008
|
+
const handleRowKeyDown = (e) => {
|
|
9009
|
+
if (!lastSessionId) return;
|
|
9010
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
9011
|
+
e.preventDefault();
|
|
9012
|
+
openLastRun();
|
|
9013
|
+
};
|
|
9014
|
+
const stopActionClick = (e) => {
|
|
9015
|
+
e.stopPropagation();
|
|
9016
|
+
};
|
|
9017
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9018
|
+
"div",
|
|
9019
|
+
{
|
|
9020
|
+
className: cn(
|
|
9021
|
+
"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",
|
|
9022
|
+
lastSessionId && "cursor-pointer"
|
|
9023
|
+
),
|
|
9024
|
+
onClick: openLastRun,
|
|
9025
|
+
onKeyDown: handleRowKeyDown,
|
|
9026
|
+
role: lastSessionId ? "button" : void 0,
|
|
9027
|
+
tabIndex: lastSessionId ? 0 : void 0,
|
|
9028
|
+
"aria-label": lastSessionId ? `Open last run for ${title}` : void 0,
|
|
9029
|
+
title: lastSessionId ? "Open last run" : void 0,
|
|
9030
|
+
children: [
|
|
9031
|
+
/* @__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" }) }),
|
|
9032
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
9033
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [
|
|
9034
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
9035
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-foreground", children: title }),
|
|
9036
|
+
/* @__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) }) }),
|
|
9037
|
+
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)) })
|
|
9038
|
+
] }),
|
|
9039
|
+
statusText && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "destructive", className: "shrink-0", children: statusText })
|
|
9040
|
+
] }),
|
|
9041
|
+
item.lastError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 line-clamp-2 text-xs text-destructive", children: item.lastError })
|
|
9042
|
+
] }),
|
|
9043
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
|
|
9044
|
+
showRunNow && /* @__PURE__ */ jsxRuntime.jsx(
|
|
9045
|
+
"button",
|
|
9046
|
+
{
|
|
9047
|
+
type: "button",
|
|
9048
|
+
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",
|
|
9049
|
+
onClick: (e) => {
|
|
9050
|
+
stopActionClick(e);
|
|
9051
|
+
onRunNow(item.id);
|
|
9052
|
+
},
|
|
9053
|
+
"aria-label": "Run schedule now",
|
|
9054
|
+
title: "Run schedule now",
|
|
9055
|
+
disabled,
|
|
9056
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarClockIcon, { className: "size-3.5" })
|
|
9057
|
+
}
|
|
9058
|
+
),
|
|
9059
|
+
canCancel && isActive && /* @__PURE__ */ jsxRuntime.jsx(
|
|
9060
|
+
"button",
|
|
9061
|
+
{
|
|
9062
|
+
type: "button",
|
|
9063
|
+
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",
|
|
9064
|
+
onClick: (e) => {
|
|
9065
|
+
stopActionClick(e);
|
|
9066
|
+
onCancel(item.id);
|
|
9067
|
+
},
|
|
9068
|
+
"aria-label": "Cancel schedule",
|
|
9069
|
+
title: "Cancel schedule",
|
|
9070
|
+
disabled,
|
|
9071
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2Icon, { className: "size-3.5" })
|
|
9072
|
+
}
|
|
9073
|
+
)
|
|
9074
|
+
] })
|
|
9075
|
+
]
|
|
9076
|
+
}
|
|
9077
|
+
);
|
|
9078
|
+
}
|
|
9079
|
+
function ScheduledWorkPanel({
|
|
9080
|
+
adapter,
|
|
9081
|
+
agentId,
|
|
9082
|
+
title = "Scheduled Work",
|
|
9083
|
+
limit = DEFAULT_LIMIT,
|
|
9084
|
+
status = "active",
|
|
9085
|
+
hideHeader = false,
|
|
9086
|
+
pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
|
|
9087
|
+
onSessionSelect,
|
|
9088
|
+
onScheduleCancel,
|
|
9089
|
+
onScheduleRunNow
|
|
9090
|
+
}) {
|
|
9091
|
+
const [items, setItems] = react.useState([]);
|
|
9092
|
+
const [error, setError] = react.useState(null);
|
|
9093
|
+
const [hasEverLoaded, setHasEverLoaded] = react.useState(false);
|
|
9094
|
+
const [actionScheduleId, setActionScheduleId] = react.useState(null);
|
|
9095
|
+
const [collapsed, setCollapsed] = react.useState(false);
|
|
9096
|
+
const mounted = react.useRef(true);
|
|
9097
|
+
const requestId = react.useRef(0);
|
|
9098
|
+
const lastFingerprint = react.useRef("");
|
|
9099
|
+
const refetch = react.useCallback(
|
|
9100
|
+
async (opts) => {
|
|
9101
|
+
if (!adapter.listScheduledWork) {
|
|
9102
|
+
setItems([]);
|
|
9103
|
+
setHasEverLoaded(true);
|
|
9104
|
+
return;
|
|
9105
|
+
}
|
|
9106
|
+
const currentRequestId = ++requestId.current;
|
|
9107
|
+
try {
|
|
9108
|
+
const list = await adapter.listScheduledWork({
|
|
9109
|
+
agentId,
|
|
9110
|
+
status,
|
|
9111
|
+
limit
|
|
9112
|
+
});
|
|
9113
|
+
if (!mounted.current || currentRequestId !== requestId.current) return;
|
|
9114
|
+
const nextItems = sortScheduledWork(list.items);
|
|
9115
|
+
const fp = fingerprint(nextItems);
|
|
9116
|
+
if (fp !== lastFingerprint.current) {
|
|
9117
|
+
lastFingerprint.current = fp;
|
|
9118
|
+
setItems(nextItems);
|
|
9119
|
+
}
|
|
9120
|
+
setError(null);
|
|
9121
|
+
setHasEverLoaded(true);
|
|
9122
|
+
} catch (e) {
|
|
9123
|
+
if (!mounted.current || currentRequestId !== requestId.current) return;
|
|
9124
|
+
if (!opts?.silent) {
|
|
9125
|
+
setError(
|
|
9126
|
+
e instanceof Error ? e.message : "Failed to load scheduled work"
|
|
9127
|
+
);
|
|
9128
|
+
setHasEverLoaded(true);
|
|
9129
|
+
}
|
|
9130
|
+
}
|
|
9131
|
+
},
|
|
9132
|
+
[adapter, agentId, limit, status]
|
|
9133
|
+
);
|
|
9134
|
+
react.useEffect(() => {
|
|
9135
|
+
mounted.current = true;
|
|
9136
|
+
return () => {
|
|
9137
|
+
mounted.current = false;
|
|
9138
|
+
};
|
|
9139
|
+
}, []);
|
|
9140
|
+
react.useEffect(() => {
|
|
9141
|
+
setError(null);
|
|
9142
|
+
lastFingerprint.current = "";
|
|
9143
|
+
void refetch();
|
|
9144
|
+
}, [refetch]);
|
|
9145
|
+
react.useEffect(() => {
|
|
9146
|
+
if (!adapter.listScheduledWork || pollIntervalMs <= 0) return;
|
|
9147
|
+
const id = setInterval(() => {
|
|
9148
|
+
void refetch({ silent: true });
|
|
9149
|
+
}, pollIntervalMs);
|
|
9150
|
+
return () => clearInterval(id);
|
|
9151
|
+
}, [adapter.listScheduledWork, pollIntervalMs, refetch]);
|
|
9152
|
+
const activeCount = react.useMemo(
|
|
9153
|
+
() => items.filter((item) => item.status === "active").length,
|
|
9154
|
+
[items]
|
|
9155
|
+
);
|
|
9156
|
+
const handleOpenLastRun = react.useCallback(
|
|
9157
|
+
(sessionId) => {
|
|
9158
|
+
onSessionSelect?.(sessionId);
|
|
9159
|
+
},
|
|
9160
|
+
[onSessionSelect]
|
|
9161
|
+
);
|
|
9162
|
+
const handleRunNow = react.useCallback(
|
|
9163
|
+
async (scheduleId) => {
|
|
9164
|
+
if (!adapter.runScheduledWorkNow || actionScheduleId) return;
|
|
9165
|
+
setActionScheduleId(scheduleId);
|
|
9166
|
+
try {
|
|
9167
|
+
const result = await adapter.runScheduledWorkNow({ scheduleId });
|
|
9168
|
+
onScheduleRunNow?.(scheduleId, result?.sessionId);
|
|
9169
|
+
await refetch({ silent: true });
|
|
9170
|
+
} catch (e) {
|
|
9171
|
+
setError(e instanceof Error ? e.message : "Failed to run schedule");
|
|
9172
|
+
} finally {
|
|
9173
|
+
if (mounted.current) setActionScheduleId(null);
|
|
9174
|
+
}
|
|
9175
|
+
},
|
|
9176
|
+
[actionScheduleId, adapter, onScheduleRunNow, refetch]
|
|
9177
|
+
);
|
|
9178
|
+
const handleCancel = react.useCallback(
|
|
9179
|
+
async (scheduleId) => {
|
|
9180
|
+
if (!adapter.cancelScheduledWork || actionScheduleId) return;
|
|
9181
|
+
setActionScheduleId(scheduleId);
|
|
9182
|
+
try {
|
|
9183
|
+
await adapter.cancelScheduledWork({ scheduleId });
|
|
9184
|
+
setItems((current) => {
|
|
9185
|
+
const next = current.filter((item) => item.id !== scheduleId);
|
|
9186
|
+
lastFingerprint.current = fingerprint(next);
|
|
9187
|
+
return next;
|
|
9188
|
+
});
|
|
9189
|
+
onScheduleCancel?.(scheduleId);
|
|
9190
|
+
await refetch({ silent: true });
|
|
9191
|
+
} catch (e) {
|
|
9192
|
+
setError(e instanceof Error ? e.message : "Failed to cancel schedule");
|
|
9193
|
+
} finally {
|
|
9194
|
+
if (mounted.current) setActionScheduleId(null);
|
|
9195
|
+
}
|
|
9196
|
+
},
|
|
9197
|
+
[actionScheduleId, adapter, onScheduleCancel, refetch]
|
|
9198
|
+
);
|
|
9199
|
+
if (!hasEverLoaded) return null;
|
|
9200
|
+
if (!adapter.listScheduledWork) return null;
|
|
9201
|
+
const showBody = hideHeader || !collapsed;
|
|
9202
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "min-w-0 border-t border-line", children: [
|
|
9203
|
+
!hideHeader && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9204
|
+
"button",
|
|
9205
|
+
{
|
|
9206
|
+
type: "button",
|
|
9207
|
+
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",
|
|
9208
|
+
onClick: () => setCollapsed((current) => !current),
|
|
9209
|
+
"aria-expanded": !collapsed,
|
|
9210
|
+
children: [
|
|
9211
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarClockIcon, { className: "size-3.5" }),
|
|
9212
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: title }),
|
|
9213
|
+
activeCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", className: "ml-auto", children: activeCount }),
|
|
9214
|
+
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" })
|
|
9215
|
+
]
|
|
9216
|
+
}
|
|
9217
|
+
),
|
|
9218
|
+
showBody && error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-xs text-destructive", children: error }),
|
|
9219
|
+
showBody && !error && items.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-xs text-faint", children: "No scheduled work" }),
|
|
9220
|
+
showBody && !error && items.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("pb-2", hideHeader && "pt-1"), children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
9221
|
+
ScheduledWorkRow,
|
|
9222
|
+
{
|
|
9223
|
+
item,
|
|
9224
|
+
canRunNow: Boolean(adapter.runScheduledWorkNow),
|
|
9225
|
+
canCancel: Boolean(adapter.cancelScheduledWork),
|
|
9226
|
+
actionScheduleId,
|
|
9227
|
+
onOpenLastRun: handleOpenLastRun,
|
|
9228
|
+
onRunNow: handleRunNow,
|
|
9229
|
+
onCancel: handleCancel
|
|
9230
|
+
},
|
|
9231
|
+
item.id
|
|
9232
|
+
)) })
|
|
9233
|
+
] });
|
|
9234
|
+
}
|
|
9235
|
+
init_utils();
|
|
8561
9236
|
var POLL_INTERVAL_ACTIVE_MS = 4e3;
|
|
8562
9237
|
var POLL_INTERVAL_IDLE_MS = 3e4;
|
|
8563
9238
|
var DEFAULT_SESSION_LIST_LIMIT = 50;
|
|
@@ -8582,7 +9257,7 @@ function buildTree(nodes) {
|
|
|
8582
9257
|
}
|
|
8583
9258
|
function treeFingerprint(nodes) {
|
|
8584
9259
|
return nodes.map(
|
|
8585
|
-
(n) => `${n.id}:${n.parentId ?? ""}:${n.status}:${n.agentType ?? ""}:${n.messageCount ?? 0}:${n.toolCallCount ?? 0}:${n.updatedAt}`
|
|
9260
|
+
(n) => `${n.id}:${n.parentId ?? ""}:${n.status}:${n.agentType ?? ""}:${n.runKind ?? ""}:${n.title ?? ""}:${n.messageCount ?? 0}:${n.toolCallCount ?? 0}:${n.updatedAt}`
|
|
8586
9261
|
).join("|");
|
|
8587
9262
|
}
|
|
8588
9263
|
function sessionToTreeNode(session) {
|
|
@@ -8592,6 +9267,7 @@ function sessionToTreeNode(session) {
|
|
|
8592
9267
|
parentId: session.parentId ?? null,
|
|
8593
9268
|
agentId: session.agentId,
|
|
8594
9269
|
channel: session.channel,
|
|
9270
|
+
runKind: session.runKind ?? deriveRunKind(session.channel, session.config),
|
|
8595
9271
|
status: session.status,
|
|
8596
9272
|
title: session.title,
|
|
8597
9273
|
model: session.model,
|
|
@@ -8640,6 +9316,23 @@ function formatSessionTime(value) {
|
|
|
8640
9316
|
if (Number.isNaN(date.getTime())) return "";
|
|
8641
9317
|
return dateFns.formatDistanceToNow(date, { addSuffix: true });
|
|
8642
9318
|
}
|
|
9319
|
+
function deriveRunKind(channel, config) {
|
|
9320
|
+
if (channel === "scheduled" && config?.scheduled_dynamic_loop === true) {
|
|
9321
|
+
return "dynamic_loop";
|
|
9322
|
+
}
|
|
9323
|
+
if (channel === "scheduled") return "scheduled";
|
|
9324
|
+
return null;
|
|
9325
|
+
}
|
|
9326
|
+
function formatRunKind(value) {
|
|
9327
|
+
if (value === "dynamic_loop") return "Loop";
|
|
9328
|
+
if (value === "scheduled") return "Scheduled";
|
|
9329
|
+
return null;
|
|
9330
|
+
}
|
|
9331
|
+
function fallbackSessionTitle(entry) {
|
|
9332
|
+
if (entry.runKind === "dynamic_loop") return "Loop run";
|
|
9333
|
+
if (entry.runKind === "scheduled") return "Scheduled run";
|
|
9334
|
+
return "New session";
|
|
9335
|
+
}
|
|
8643
9336
|
function TreeNodeRow({
|
|
8644
9337
|
entry,
|
|
8645
9338
|
depth,
|
|
@@ -8655,9 +9348,11 @@ function TreeNodeRow({
|
|
|
8655
9348
|
const hasChildren = entry.children.length > 0;
|
|
8656
9349
|
const isActive = entry.id === activeSessionId;
|
|
8657
9350
|
const isRunning = entry.status === "active";
|
|
8658
|
-
const
|
|
8659
|
-
const title = entry.title ??
|
|
9351
|
+
const isChildSession = entry.parentId != null;
|
|
9352
|
+
const title = entry.title ?? fallbackSessionTitle(entry);
|
|
8660
9353
|
const subtitle = [
|
|
9354
|
+
formatRunKind(entry.runKind),
|
|
9355
|
+
entry.agentType,
|
|
8661
9356
|
entry.model ?? "default",
|
|
8662
9357
|
formatSessionTime(entry.updatedAt)
|
|
8663
9358
|
].filter(Boolean).join(" \xB7 ");
|
|
@@ -8695,7 +9390,7 @@ function TreeNodeRow({
|
|
|
8695
9390
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm truncate", children: title }),
|
|
8696
9391
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-faint truncate", children: subtitle })
|
|
8697
9392
|
] }),
|
|
8698
|
-
isRunning && canStop &&
|
|
9393
|
+
isRunning && canStop && isChildSession && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8699
9394
|
"button",
|
|
8700
9395
|
{
|
|
8701
9396
|
type: "button",
|
|
@@ -8704,8 +9399,8 @@ function TreeNodeRow({
|
|
|
8704
9399
|
e.stopPropagation();
|
|
8705
9400
|
onStop(entry.id);
|
|
8706
9401
|
},
|
|
8707
|
-
"aria-label": "Stop
|
|
8708
|
-
title: "Stop
|
|
9402
|
+
"aria-label": "Stop child session",
|
|
9403
|
+
title: "Stop child session",
|
|
8709
9404
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SquareIcon, { className: "w-3 h-3", fill: "currentColor" })
|
|
8710
9405
|
}
|
|
8711
9406
|
),
|
|
@@ -8909,6 +9604,7 @@ function SessionTreePanel({
|
|
|
8909
9604
|
exports.AgentChat = AgentChat;
|
|
8910
9605
|
exports.AgentChatAdapterProvider = AgentChatAdapterProvider;
|
|
8911
9606
|
exports.MessageResponse = MessageResponse;
|
|
9607
|
+
exports.ScheduledWorkPanel = ScheduledWorkPanel;
|
|
8912
9608
|
exports.SessionTreePanel = SessionTreePanel;
|
|
8913
9609
|
exports.useAgentChatAdapterContext = useAgentChatAdapterContext;
|
|
8914
9610
|
exports.useAgentChatRuntime = useAgentChatRuntime;
|