@invergent/agent-chat-react 2.16.8 → 2.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1225 -429
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +249 -6
- package/dist/index.d.ts +249 -6
- package/dist/index.js +1225 -434
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -215,6 +215,70 @@ function useAgentChatAdapterContext() {
|
|
|
215
215
|
}
|
|
216
216
|
return value;
|
|
217
217
|
}
|
|
218
|
+
function useProjectThreads(adapter, projectId) {
|
|
219
|
+
const [rows, setRows] = react.useState({});
|
|
220
|
+
react.useEffect(() => {
|
|
221
|
+
setRows({});
|
|
222
|
+
if (!projectId || !adapter.listProjectThreads || !adapter.openProjectStream) return;
|
|
223
|
+
let closed = false;
|
|
224
|
+
let reading = false;
|
|
225
|
+
let wanted = /* @__PURE__ */ new Set();
|
|
226
|
+
const failed = /* @__PURE__ */ new Set();
|
|
227
|
+
const readOne = async (threadId) => {
|
|
228
|
+
if (threadId === null) {
|
|
229
|
+
const all = await adapter.listProjectThreads({ projectId });
|
|
230
|
+
if (!closed) setRows(Object.fromEntries(all.map((row2) => [row2.id, row2])));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const [row] = await adapter.listProjectThreads({ projectId, threadId });
|
|
234
|
+
if (!closed) setRows(({ [threadId]: _gone, ...rest }) => row ? { ...rest, [threadId]: row } : rest);
|
|
235
|
+
};
|
|
236
|
+
const read = async () => {
|
|
237
|
+
if (reading) return;
|
|
238
|
+
reading = true;
|
|
239
|
+
while (wanted.size > 0 && !closed) {
|
|
240
|
+
const asked = wanted;
|
|
241
|
+
wanted = /* @__PURE__ */ new Set();
|
|
242
|
+
for (const threadId of asked.has(null) ? [null] : asked) {
|
|
243
|
+
if (closed) break;
|
|
244
|
+
try {
|
|
245
|
+
await readOne(threadId);
|
|
246
|
+
} catch {
|
|
247
|
+
failed.add(threadId);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
reading = false;
|
|
252
|
+
};
|
|
253
|
+
const want = (threadId) => {
|
|
254
|
+
wanted.add(threadId);
|
|
255
|
+
for (const again of failed) wanted.add(again);
|
|
256
|
+
failed.clear();
|
|
257
|
+
void read();
|
|
258
|
+
};
|
|
259
|
+
const stream = adapter.openProjectStream({ projectId });
|
|
260
|
+
stream.onerror = () => {
|
|
261
|
+
if (closed) return;
|
|
262
|
+
closed = true;
|
|
263
|
+
setRows({});
|
|
264
|
+
};
|
|
265
|
+
stream.addEventListener("ready", () => want(null));
|
|
266
|
+
stream.addEventListener("change", (event) => {
|
|
267
|
+
let threadId = null;
|
|
268
|
+
try {
|
|
269
|
+
const named2 = JSON.parse(event.data).thread_id;
|
|
270
|
+
threadId = typeof named2 === "string" ? named2 : null;
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
want(threadId);
|
|
274
|
+
});
|
|
275
|
+
return () => {
|
|
276
|
+
closed = true;
|
|
277
|
+
stream.close();
|
|
278
|
+
};
|
|
279
|
+
}, [adapter, projectId]);
|
|
280
|
+
return rows;
|
|
281
|
+
}
|
|
218
282
|
var NAMED_KEYS = /* @__PURE__ */ new Set([
|
|
219
283
|
"Enter",
|
|
220
284
|
"Tab",
|
|
@@ -423,7 +487,7 @@ function BrowserShell({
|
|
|
423
487
|
children: /* @__PURE__ */ jsxRuntime.jsx(Reload, {})
|
|
424
488
|
}
|
|
425
489
|
),
|
|
426
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
490
|
+
(onToggleControl || hasControl) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
427
491
|
"button",
|
|
428
492
|
{
|
|
429
493
|
type: "button",
|
|
@@ -947,11 +1011,26 @@ function ConfirmDialog({
|
|
|
947
1011
|
}
|
|
948
1012
|
);
|
|
949
1013
|
}
|
|
1014
|
+
function ComputerBrowserPane({
|
|
1015
|
+
available,
|
|
1016
|
+
readOnly,
|
|
1017
|
+
draw
|
|
1018
|
+
}) {
|
|
1019
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1020
|
+
"div",
|
|
1021
|
+
{
|
|
1022
|
+
"data-testid": "browser-pane",
|
|
1023
|
+
className: "flex h-full min-h-0 flex-col items-center justify-center gap-3 bg-background p-6 text-center text-sm text-muted-foreground",
|
|
1024
|
+
children: draw ? draw({ available, readOnly }) : available ? "The browser is open on the chat's computer." : "The chat's computer has no supported browser."
|
|
1025
|
+
}
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
950
1028
|
function BrowserPane({
|
|
951
1029
|
sessionId,
|
|
952
1030
|
state,
|
|
953
1031
|
adapter,
|
|
954
|
-
onClose
|
|
1032
|
+
onClose,
|
|
1033
|
+
readOnly = false
|
|
955
1034
|
}) {
|
|
956
1035
|
const [fullscreenOpen, setFullscreenOpen] = react.useState(false);
|
|
957
1036
|
const [openFullscreenOnControl, setOpenFullscreenOnControl] = react.useState(false);
|
|
@@ -1008,8 +1087,8 @@ function BrowserPane({
|
|
|
1008
1087
|
src: shellUrl,
|
|
1009
1088
|
testId,
|
|
1010
1089
|
hasControl: localControlActive,
|
|
1011
|
-
onToggleControl: hasControlAdapter ? () => void control.toggleControl() : void 0,
|
|
1012
|
-
onClose: onClose ? () => control.setConfirmOpen(true) : void 0,
|
|
1090
|
+
onToggleControl: hasControlAdapter && !readOnly ? () => void control.toggleControl() : void 0,
|
|
1091
|
+
onClose: onClose && !readOnly ? () => control.setConfirmOpen(true) : void 0,
|
|
1013
1092
|
onMaximize: fullscreenOpen ? void 0 : () => setFullscreenOpen(true),
|
|
1014
1093
|
onDisconnect: () => setLocalControlActive(false)
|
|
1015
1094
|
}
|
|
@@ -4180,13 +4259,13 @@ function Textarea({ className, ...props }) {
|
|
|
4180
4259
|
var MAX_REASON_LENGTH = 500;
|
|
4181
4260
|
function ExpertToolBlock({ tc }) {
|
|
4182
4261
|
const [expanded, setExpanded] = react.useState(false);
|
|
4183
|
-
const { adapter, sessionId } = useAgentChatAdapterContext();
|
|
4262
|
+
const { adapter, sessionId, readOnly } = useAgentChatAdapterContext();
|
|
4184
4263
|
const [pending, setPending] = react.useState(null);
|
|
4185
4264
|
const [error, setError] = react.useState(null);
|
|
4186
4265
|
const [reasonDraft, setReasonDraft] = react.useState(null);
|
|
4187
4266
|
const rating = tc.expertFeedback?.rating ?? pending;
|
|
4188
4267
|
const alreadyRated = tc.expertFeedback !== void 0;
|
|
4189
|
-
const canRate = tc.expertResultEventId !== void 0 && sessionId !== null && adapter.submitExpertFeedback !== void 0 && tc.status !== "running";
|
|
4268
|
+
const canRate = !readOnly && tc.expertResultEventId !== void 0 && sessionId !== null && adapter.submitExpertFeedback !== void 0 && tc.status !== "running";
|
|
4190
4269
|
const submit = async (next, reason) => {
|
|
4191
4270
|
if (sessionId === null || tc.expertResultEventId === void 0 || adapter.submitExpertFeedback === void 0) return;
|
|
4192
4271
|
setPending(next);
|
|
@@ -4548,11 +4627,12 @@ function ConversationalAsk({
|
|
|
4548
4627
|
question,
|
|
4549
4628
|
assistantContent
|
|
4550
4629
|
}) {
|
|
4551
|
-
const { adapter, sessionId } = useAgentChatAdapterContext();
|
|
4630
|
+
const { adapter, sessionId, readOnly } = useAgentChatAdapterContext();
|
|
4552
4631
|
const [submitting, setSubmitting] = react.useState(false);
|
|
4553
4632
|
const [error, setError] = react.useState(null);
|
|
4554
4633
|
const answered = tc.askUserQuestionAnswers !== void 0;
|
|
4555
|
-
const
|
|
4634
|
+
const open = !answered && tc.status === "running";
|
|
4635
|
+
const pending = open && !readOnly;
|
|
4556
4636
|
const choices = question.choices ?? [];
|
|
4557
4637
|
const showPrompt = react.useMemo(
|
|
4558
4638
|
() => !promptEchoedInContent(assistantContent, question.prompt),
|
|
@@ -4614,7 +4694,7 @@ function ConversationalAsk({
|
|
|
4614
4694
|
}
|
|
4615
4695
|
),
|
|
4616
4696
|
error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: error }),
|
|
4617
|
-
!answered && !
|
|
4697
|
+
!answered && !open && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs italic text-muted-foreground/70", children: "No answer recorded." })
|
|
4618
4698
|
] });
|
|
4619
4699
|
}
|
|
4620
4700
|
function QuickReply({
|
|
@@ -4648,14 +4728,14 @@ function BatchAsk({
|
|
|
4648
4728
|
questions,
|
|
4649
4729
|
viewMode
|
|
4650
4730
|
}) {
|
|
4651
|
-
const { adapter, sessionId } = useAgentChatAdapterContext();
|
|
4731
|
+
const { adapter, sessionId, readOnly } = useAgentChatAdapterContext();
|
|
4652
4732
|
const [active, setActive] = react.useState(0);
|
|
4653
4733
|
const [selections, setSelections] = react.useState(
|
|
4654
4734
|
() => questions.map(emptySelection)
|
|
4655
4735
|
);
|
|
4656
4736
|
const [submitting, setSubmitting] = react.useState(false);
|
|
4657
4737
|
const [error, setError] = react.useState(null);
|
|
4658
|
-
const locked = tc.askUserQuestionAnswers !== void 0 || tc.status !== "running";
|
|
4738
|
+
const locked = readOnly || tc.askUserQuestionAnswers !== void 0 || tc.status !== "running";
|
|
4659
4739
|
react.useEffect(() => {
|
|
4660
4740
|
if (active >= questions.length && questions.length > 0) setActive(0);
|
|
4661
4741
|
}, [active, questions.length]);
|
|
@@ -4955,7 +5035,7 @@ function BatchAskLocked({
|
|
|
4955
5035
|
);
|
|
4956
5036
|
const cancelled = !answers && tc.status !== "running";
|
|
4957
5037
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border border-border bg-muted/20 px-3 py-2 text-sm max-w-2xl", children: [
|
|
4958
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1 text-xs text-muted-foreground", children: cancelled ? "Clarification cancelled" : "Clarification answered" }),
|
|
5038
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1 text-xs text-muted-foreground", children: cancelled ? "Clarification cancelled" : answers ? "Clarification answered" : "Waiting for an answer" }),
|
|
4959
5039
|
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-1.5", children: questions.map((q, i) => {
|
|
4960
5040
|
const a = byPrompt.get(q.prompt);
|
|
4961
5041
|
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "text-sm", children: [
|
|
@@ -6208,6 +6288,62 @@ function CommandItem({
|
|
|
6208
6288
|
);
|
|
6209
6289
|
}
|
|
6210
6290
|
|
|
6291
|
+
// src/components/ui/dropdown-menu.tsx
|
|
6292
|
+
init_utils();
|
|
6293
|
+
function DropdownMenu({
|
|
6294
|
+
...props
|
|
6295
|
+
}) {
|
|
6296
|
+
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.DropdownMenu.Root, { "data-slot": "dropdown-menu", ...props });
|
|
6297
|
+
}
|
|
6298
|
+
function DropdownMenuTrigger({
|
|
6299
|
+
...props
|
|
6300
|
+
}) {
|
|
6301
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
6302
|
+
radixUi.DropdownMenu.Trigger,
|
|
6303
|
+
{
|
|
6304
|
+
"data-slot": "dropdown-menu-trigger",
|
|
6305
|
+
...props
|
|
6306
|
+
}
|
|
6307
|
+
);
|
|
6308
|
+
}
|
|
6309
|
+
function DropdownMenuContent({
|
|
6310
|
+
className,
|
|
6311
|
+
align = "start",
|
|
6312
|
+
sideOffset = 4,
|
|
6313
|
+
...props
|
|
6314
|
+
}) {
|
|
6315
|
+
return /* @__PURE__ */ jsxRuntime.jsx(radixUi.DropdownMenu.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6316
|
+
radixUi.DropdownMenu.Content,
|
|
6317
|
+
{
|
|
6318
|
+
"data-slot": "dropdown-menu-content",
|
|
6319
|
+
sideOffset,
|
|
6320
|
+
align,
|
|
6321
|
+
className: cn("z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-48 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-none bg-popover p-1.5 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
|
|
6322
|
+
...props
|
|
6323
|
+
}
|
|
6324
|
+
) });
|
|
6325
|
+
}
|
|
6326
|
+
function DropdownMenuItem({
|
|
6327
|
+
className,
|
|
6328
|
+
inset,
|
|
6329
|
+
variant = "default",
|
|
6330
|
+
...props
|
|
6331
|
+
}) {
|
|
6332
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
6333
|
+
radixUi.DropdownMenu.Item,
|
|
6334
|
+
{
|
|
6335
|
+
"data-slot": "dropdown-menu-item",
|
|
6336
|
+
"data-inset": inset,
|
|
6337
|
+
"data-variant": variant,
|
|
6338
|
+
className: cn(
|
|
6339
|
+
"group/dropdown-menu-item relative flex cursor-default items-center gap-2.5 rounded-none px-3 py-2 text-xs font-medium tracking-wider uppercase outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-9.5 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 data-[variant=destructive]:*:[svg]:text-destructive",
|
|
6340
|
+
className
|
|
6341
|
+
),
|
|
6342
|
+
...props
|
|
6343
|
+
}
|
|
6344
|
+
);
|
|
6345
|
+
}
|
|
6346
|
+
|
|
6211
6347
|
// src/components/ui/spinner.tsx
|
|
6212
6348
|
init_utils();
|
|
6213
6349
|
function Spinner({ className, ...props }) {
|
|
@@ -8489,12 +8625,12 @@ function BrandBeam({
|
|
|
8489
8625
|
borderRadius,
|
|
8490
8626
|
className
|
|
8491
8627
|
}) {
|
|
8492
|
-
const
|
|
8628
|
+
const reducedMotion2 = react.useSyncExternalStore(
|
|
8493
8629
|
subscribeReducedMotion,
|
|
8494
8630
|
readReducedMotion,
|
|
8495
8631
|
readReducedMotionServer
|
|
8496
8632
|
);
|
|
8497
|
-
const effectiveActive = active && !(
|
|
8633
|
+
const effectiveActive = active && !(reducedMotion2 && ROTATE_SIZES.has(size2));
|
|
8498
8634
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
8499
8635
|
BorderBeam,
|
|
8500
8636
|
{
|
|
@@ -10921,7 +11057,9 @@ var MAX_CPS = 240;
|
|
|
10921
11057
|
var DRAIN_SECONDS = 0.25;
|
|
10922
11058
|
var SNAP_LAG = 800;
|
|
10923
11059
|
var MAX_FRAME_DT = 0.1;
|
|
11060
|
+
var reducedMotion = () => typeof document !== "undefined" && document.documentElement.dataset.motion === "reduced";
|
|
10924
11061
|
function useSmoothStream(target, isStreaming) {
|
|
11062
|
+
const instant = reducedMotion();
|
|
10925
11063
|
const [displayedLen, setDisplayedLen] = react.useState(
|
|
10926
11064
|
() => isStreaming ? 0 : target.length
|
|
10927
11065
|
);
|
|
@@ -10930,7 +11068,7 @@ function useSmoothStream(target, isStreaming) {
|
|
|
10930
11068
|
react.useEffect(() => {
|
|
10931
11069
|
setDisplayedLen((cur) => cur > target.length ? target.length : cur);
|
|
10932
11070
|
}, [target]);
|
|
10933
|
-
const isActive = isStreaming || displayedLen < target.length;
|
|
11071
|
+
const isActive = !instant && (isStreaming || displayedLen < target.length);
|
|
10934
11072
|
react.useEffect(() => {
|
|
10935
11073
|
if (!isActive) return;
|
|
10936
11074
|
let raf = 0;
|
|
@@ -10962,7 +11100,7 @@ function useSmoothStream(target, isStreaming) {
|
|
|
10962
11100
|
raf = requestAnimationFrame(tick);
|
|
10963
11101
|
return () => cancelAnimationFrame(raf);
|
|
10964
11102
|
}, [isActive]);
|
|
10965
|
-
return displayedLen >= target.length ? target : target.slice(0, displayedLen);
|
|
11103
|
+
return instant || displayedLen >= target.length ? target : target.slice(0, displayedLen);
|
|
10966
11104
|
}
|
|
10967
11105
|
|
|
10968
11106
|
// src/runtime/reasoning-tokens.ts
|
|
@@ -12045,6 +12183,304 @@ function ArtifactRow({
|
|
|
12045
12183
|
}
|
|
12046
12184
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
|
|
12047
12185
|
}
|
|
12186
|
+
var GROUP_LABEL = {
|
|
12187
|
+
waiting: "Waiting on you",
|
|
12188
|
+
working: "Working",
|
|
12189
|
+
idle: "Idle",
|
|
12190
|
+
resolved: "Resolved"
|
|
12191
|
+
};
|
|
12192
|
+
var STATE_LABEL = {
|
|
12193
|
+
working: "Working",
|
|
12194
|
+
reported: "Reported",
|
|
12195
|
+
failed: "Failed"
|
|
12196
|
+
};
|
|
12197
|
+
var FILES_SHOWN = 3;
|
|
12198
|
+
var MARKS = {
|
|
12199
|
+
landed: null,
|
|
12200
|
+
redoing: "being redone",
|
|
12201
|
+
not_merged: "not merged",
|
|
12202
|
+
undone: "undone"
|
|
12203
|
+
};
|
|
12204
|
+
var changedBy = (by) => by.kind === "you" ? "you" : by.kind === "thread" ? `the thread ${by.title}` : `the routine ${by.name}`;
|
|
12205
|
+
function named(paths) {
|
|
12206
|
+
const names = paths.slice(0, FILES_SHOWN);
|
|
12207
|
+
if (paths.length > FILES_SHOWN) names.push(`${paths.length - FILES_SHOWN} more files`);
|
|
12208
|
+
return names.length === 1 ? names[0] : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
|
|
12209
|
+
}
|
|
12210
|
+
function undoLines(result) {
|
|
12211
|
+
const lines = result.applied.length > 0 ? [`Put back ${named(result.applied)}.`] : [];
|
|
12212
|
+
const picked = result.pickedUp ?? [];
|
|
12213
|
+
if (picked.length > 0) lines.push(`Your changes to ${named(picked)} were recorded first: each is a version in its History.`);
|
|
12214
|
+
for (const { path, by, pruned } of result.skipped.slice(0, FILES_SHOWN)) {
|
|
12215
|
+
lines.push(pruned ? `${path} could not be put back: its version from before is no longer kept.` : by === null ? `${path} was left as it is.` : `${path} was changed after this, by ${changedBy(by)}. It was left as it is. Restore an earlier version from its History.`);
|
|
12216
|
+
}
|
|
12217
|
+
const more = result.skipped.length - FILES_SHOWN;
|
|
12218
|
+
if (more === 1) lines.push("1 more file was left as it is: its History says who changed it.");
|
|
12219
|
+
if (more > 1) lines.push(`${more} more files were left as they are: each one's History says who changed it.`);
|
|
12220
|
+
return lines.length > 0 ? lines : ["Nothing to put back: the files are as they were."];
|
|
12221
|
+
}
|
|
12222
|
+
var ASIDE_WHAT = {
|
|
12223
|
+
copy: "its copy",
|
|
12224
|
+
repository: "its repository",
|
|
12225
|
+
place: "the earlier history at its folder's path, with its threads' copies"
|
|
12226
|
+
};
|
|
12227
|
+
function setAsideWhen(at) {
|
|
12228
|
+
const parts = new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).formatToParts(new Date(at));
|
|
12229
|
+
const part = (type) => parts.find((found) => found.type === type)?.value ?? "";
|
|
12230
|
+
return `${part("day")} ${part("month")}, ${part("hour")}:${part("minute")}`;
|
|
12231
|
+
}
|
|
12232
|
+
function setAsideLines(aside) {
|
|
12233
|
+
const each = (entries) => (Array.isArray(entries) ? entries : []).flatMap((entry) => {
|
|
12234
|
+
const { kind, at } = typeof entry === "object" && entry !== null ? entry : {};
|
|
12235
|
+
const moment = typeof at === "string" ? Date.parse(at) : NaN;
|
|
12236
|
+
const what = typeof kind === "string" && Object.hasOwn(ASIDE_WHAT, kind) ? ASIDE_WHAT[kind] : void 0;
|
|
12237
|
+
return what !== void 0 && !Number.isNaN(moment) ? [`${what} from ${setAsideWhen(moment)}`] : [];
|
|
12238
|
+
});
|
|
12239
|
+
if (!aside) return [];
|
|
12240
|
+
const count = Number.isSafeInteger(aside.snapshots) && aside.snapshots > 0 ? aside.snapshots : 0;
|
|
12241
|
+
const kept = [...each(aside.kept), ...count > 0 ? [`${count} snapshot${count > 1 ? "s" : ""}`] : []];
|
|
12242
|
+
const gone = each(aside.gone);
|
|
12243
|
+
return [
|
|
12244
|
+
...kept.length > 0 ? [`Kept on its computer: ${kept.join(" \xB7 ")}`] : [],
|
|
12245
|
+
...gone.length > 0 ? [`No longer kept: ${gone.join(" \xB7 ")}`] : []
|
|
12246
|
+
];
|
|
12247
|
+
}
|
|
12248
|
+
function firstLine3(text) {
|
|
12249
|
+
return text?.split("\n").find((line) => line.trim())?.trim() ?? null;
|
|
12250
|
+
}
|
|
12251
|
+
function statusLineOf(report) {
|
|
12252
|
+
for (const line of report?.split("\n") ?? []) {
|
|
12253
|
+
if (/^\s*#{1,6}(\s|$)/.test(line)) continue;
|
|
12254
|
+
const plain = line.replace(/\*\*|__|`/g, "").trim();
|
|
12255
|
+
if (plain) return plain;
|
|
12256
|
+
}
|
|
12257
|
+
return null;
|
|
12258
|
+
}
|
|
12259
|
+
var cardStarts = /* @__PURE__ */ new WeakMap();
|
|
12260
|
+
var startListeners = /* @__PURE__ */ new Set();
|
|
12261
|
+
var startsChanged = 0;
|
|
12262
|
+
var drawnCards = /* @__PURE__ */ new Map();
|
|
12263
|
+
function setCardStart(adapter, card, start) {
|
|
12264
|
+
const starts = cardStarts.get(adapter) ?? /* @__PURE__ */ new Map();
|
|
12265
|
+
cardStarts.set(adapter, starts.set(card, start));
|
|
12266
|
+
startsChanged++;
|
|
12267
|
+
for (const listener of startListeners) listener();
|
|
12268
|
+
}
|
|
12269
|
+
function useCardStarts(adapter) {
|
|
12270
|
+
react.useSyncExternalStore(
|
|
12271
|
+
(listener) => {
|
|
12272
|
+
startListeners.add(listener);
|
|
12273
|
+
return () => {
|
|
12274
|
+
startListeners.delete(listener);
|
|
12275
|
+
};
|
|
12276
|
+
},
|
|
12277
|
+
() => startsChanged
|
|
12278
|
+
);
|
|
12279
|
+
return cardStarts.get(adapter) ?? /* @__PURE__ */ new Map();
|
|
12280
|
+
}
|
|
12281
|
+
function ThreadCards({ message }) {
|
|
12282
|
+
if (message.worker) return /* @__PURE__ */ jsxRuntime.jsx(WorkerCard, { worker: message.worker });
|
|
12283
|
+
if (message.proposal) return /* @__PURE__ */ jsxRuntime.jsx(ProposalCard, { proposal: message.proposal });
|
|
12284
|
+
return null;
|
|
12285
|
+
}
|
|
12286
|
+
function WorkerCard({ worker }) {
|
|
12287
|
+
const { adapter, projectId, onFileSelect, onOpenSession, threadRows } = useAgentChatAdapterContext();
|
|
12288
|
+
const undoKey = `undo:${worker.id}`;
|
|
12289
|
+
const undone = useCardStarts(adapter).get(undoKey);
|
|
12290
|
+
const live = threadRows?.[worker.id];
|
|
12291
|
+
const group = live?.group && Object.hasOwn(GROUP_LABEL, live.group) ? live.group : void 0;
|
|
12292
|
+
const status = live ? [group && GROUP_LABEL[group], live.progress && `${live.progress.done}/${live.progress.total}`].filter(Boolean).join(" \xB7 ") : STATE_LABEL[worker.state];
|
|
12293
|
+
const statusLine = live ? live.statusLine : statusLineOf(worker.report);
|
|
12294
|
+
const files = live ? Array.isArray(live.files) ? live.files : [] : worker.files;
|
|
12295
|
+
const unlisted = live && Number.isSafeInteger(live.moreFiles) && live.moreFiles > 0 ? live.moreFiles : 0;
|
|
12296
|
+
const more = Math.max(files.length - FILES_SHOWN, 0) + unlisted;
|
|
12297
|
+
const setAside = setAsideLines(live?.setAside);
|
|
12298
|
+
const name = worker.title ?? firstLine3(worker.goal);
|
|
12299
|
+
const view = worker.title !== null ? "View thread" : "View worker";
|
|
12300
|
+
const canUndo = !!live && !!projectId && !!adapter.undoProjectChanges;
|
|
12301
|
+
const undoingNow = undone?.state === "undoing";
|
|
12302
|
+
const undo = async (target) => {
|
|
12303
|
+
if (undoingNow) return;
|
|
12304
|
+
setCardStart(adapter, undoKey, { state: "undoing" });
|
|
12305
|
+
try {
|
|
12306
|
+
setCardStart(adapter, undoKey, { state: "undone", result: await adapter.undoProjectChanges({ projectId, target }) });
|
|
12307
|
+
} catch (error) {
|
|
12308
|
+
setCardStart(adapter, undoKey, { state: "not_undone", error: error instanceof Error ? error.message : "The change could not be undone." });
|
|
12309
|
+
}
|
|
12310
|
+
};
|
|
12311
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": "worker-card", "data-group": group, className: "my-2 rounded-lg border border-border px-3 py-2 text-sm", children: [
|
|
12312
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
12313
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", title: name ?? void 0, children: /* @__PURE__ */ jsxRuntime.jsx("bdi", { children: name }) }),
|
|
12314
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": "worker-card-status", className: "shrink-0 text-xs text-muted-foreground", children: status })
|
|
12315
|
+
] }),
|
|
12316
|
+
statusLine && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-foreground/70", title: statusLine, children: /* @__PURE__ */ jsxRuntime.jsx("bdi", { children: statusLine }) }),
|
|
12317
|
+
files.length + unlisted > 0 && /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "mt-1 flex flex-wrap gap-x-3 text-xs", children: [
|
|
12318
|
+
files.slice(0, FILES_SHOWN).map((file) => {
|
|
12319
|
+
const landing = "landing" in file && file.landing && Object.hasOwn(MARKS, file.landing) ? file.landing : null;
|
|
12320
|
+
const mark = landing ? MARKS[landing] : null;
|
|
12321
|
+
return (
|
|
12322
|
+
// A long name is cut, its mark never.
|
|
12323
|
+
/* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex min-w-0 max-w-full", title: file.label, children: [
|
|
12324
|
+
file.kind === "file" && onFileSelect ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
12325
|
+
"button",
|
|
12326
|
+
{
|
|
12327
|
+
type: "button",
|
|
12328
|
+
className: "min-w-0 truncate underline",
|
|
12329
|
+
onClick: () => onFileSelect(file.ref),
|
|
12330
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("bdi", { children: file.label })
|
|
12331
|
+
}
|
|
12332
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: /* @__PURE__ */ jsxRuntime.jsx("bdi", { children: file.label }) }),
|
|
12333
|
+
mark && /* @__PURE__ */ jsxRuntime.jsx("span", { "data-mark": landing, className: "shrink-0 whitespace-pre text-muted-foreground", children: ` \xB7 ${mark}` })
|
|
12334
|
+
] }, `${file.kind}:${file.ref}`)
|
|
12335
|
+
);
|
|
12336
|
+
}),
|
|
12337
|
+
more > 0 && /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
12338
|
+
"+",
|
|
12339
|
+
more,
|
|
12340
|
+
" more"
|
|
12341
|
+
] })
|
|
12342
|
+
] }),
|
|
12343
|
+
setAside.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "worker-card-set-aside", className: "mt-1 text-xs text-muted-foreground", children: setAside.map((line, at) => /* @__PURE__ */ jsxRuntime.jsx("p", { children: line }, at)) }),
|
|
12344
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex items-center gap-1", children: [
|
|
12345
|
+
onOpenSession && /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "xs", variant: "ghost", "aria-label": name ? `${view} ${name}` : void 0, onClick: () => onOpenSession(worker.id), children: view }),
|
|
12346
|
+
canUndo && live.landingId && /* @__PURE__ */ jsxRuntime.jsx(
|
|
12347
|
+
Button,
|
|
12348
|
+
{
|
|
12349
|
+
size: "xs",
|
|
12350
|
+
variant: "ghost",
|
|
12351
|
+
disabled: undoingNow,
|
|
12352
|
+
"aria-label": name ? `Undo ${name}'s latest changes` : void 0,
|
|
12353
|
+
onClick: () => void undo({ landingId: live.landingId }),
|
|
12354
|
+
children: "Undo"
|
|
12355
|
+
}
|
|
12356
|
+
),
|
|
12357
|
+
canUndo && /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
12358
|
+
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "xs", variant: "ghost", "aria-label": name ? `More for ${name}` : "More", children: "\u2026" }) }),
|
|
12359
|
+
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuContent, { children: /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuItem, { disabled: undoingNow, onSelect: () => void undo({ threadId: worker.id }), children: "Undo this thread's changes" }) })
|
|
12360
|
+
] })
|
|
12361
|
+
] }),
|
|
12362
|
+
canUndo && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", className: "text-xs text-foreground/70", children: undone?.state === "undone" && undoLines(undone.result).map((line, at) => /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("bdi", { children: line }) }, at)) }),
|
|
12363
|
+
canUndo && undone?.state === "not_undone" && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-xs text-destructive", children: /* @__PURE__ */ jsxRuntime.jsx("bdi", { children: undone.error }) })
|
|
12364
|
+
] });
|
|
12365
|
+
}
|
|
12366
|
+
function ProposalCard({ proposal }) {
|
|
12367
|
+
const { adapter, projectId, onOpenSession } = useAgentChatAdapterContext();
|
|
12368
|
+
const starts = useCardStarts(adapter);
|
|
12369
|
+
const startOf = (key) => starts.get(`${proposal.proposalId}:${key}`);
|
|
12370
|
+
const told = (key, start2) => setCardStart(adapter, `${proposal.proposalId}:${key}`, start2);
|
|
12371
|
+
const started = {};
|
|
12372
|
+
for (const { key } of proposal.threads) {
|
|
12373
|
+
const start2 = startOf(key);
|
|
12374
|
+
if (start2?.state === "started") started[key] = start2.threadId;
|
|
12375
|
+
}
|
|
12376
|
+
Object.assign(started, proposal.started);
|
|
12377
|
+
const startingAt = (key) => {
|
|
12378
|
+
const start2 = startOf(key);
|
|
12379
|
+
return start2?.state === "starting" ? start2.where : void 0;
|
|
12380
|
+
};
|
|
12381
|
+
const starting = (key) => startingAt(key) !== void 0;
|
|
12382
|
+
const canStart = !!projectId && !!adapter.startProposedThread;
|
|
12383
|
+
const canStartHere = !!projectId && !!adapter.startLocalThread;
|
|
12384
|
+
const viewButtons = react.useRef({});
|
|
12385
|
+
react.useEffect(() => {
|
|
12386
|
+
drawnCards.set(proposal.proposalId, (drawnCards.get(proposal.proposalId) ?? 0) + 1);
|
|
12387
|
+
return () => {
|
|
12388
|
+
const left = (drawnCards.get(proposal.proposalId) ?? 1) - 1;
|
|
12389
|
+
if (left > 0) drawnCards.set(proposal.proposalId, left);
|
|
12390
|
+
else drawnCards.delete(proposal.proposalId);
|
|
12391
|
+
};
|
|
12392
|
+
}, [proposal.proposalId]);
|
|
12393
|
+
react.useEffect(() => {
|
|
12394
|
+
for (const { key } of proposal.threads) {
|
|
12395
|
+
const start2 = startOf(key);
|
|
12396
|
+
if (start2?.state !== "started" || !start2.focus) continue;
|
|
12397
|
+
if (!document.activeElement || document.activeElement === document.body) viewButtons.current[key]?.focus();
|
|
12398
|
+
told(key, { ...start2, focus: false });
|
|
12399
|
+
}
|
|
12400
|
+
});
|
|
12401
|
+
const start = async (key, title) => {
|
|
12402
|
+
told(key, { state: "starting", where: title === void 0 ? "cloud" : "device" });
|
|
12403
|
+
try {
|
|
12404
|
+
const card = { projectId, proposalId: proposal.proposalId, key };
|
|
12405
|
+
const row = title === void 0 ? await adapter.startProposedThread(card) : await adapter.startLocalThread({ ...card, title });
|
|
12406
|
+
told(key, { state: "started", threadId: row.id, focus: drawnCards.has(proposal.proposalId) });
|
|
12407
|
+
} catch (error) {
|
|
12408
|
+
told(key, { state: "failed", error: error instanceof Error ? error.message : "The thread could not be started." });
|
|
12409
|
+
}
|
|
12410
|
+
};
|
|
12411
|
+
const startable = proposal.threads.filter(
|
|
12412
|
+
(thread) => thread.where === "cloud" && !started[thread.key] && !starting(thread.key)
|
|
12413
|
+
);
|
|
12414
|
+
const startAll = async (keys) => {
|
|
12415
|
+
for (const key of keys) told(key, { state: "starting", where: "cloud" });
|
|
12416
|
+
for (const key of keys) await start(key);
|
|
12417
|
+
};
|
|
12418
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": "thread-proposal-card", className: "my-2 rounded-lg border border-border px-3 py-2 text-sm", children: [
|
|
12419
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
12420
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 font-medium text-foreground", children: "Proposed threads" }),
|
|
12421
|
+
canStart && startable.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "xs", onClick: () => void startAll(startable.map((thread) => thread.key)), children: "Start all" })
|
|
12422
|
+
] }),
|
|
12423
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-1 space-y-2", children: proposal.threads.map((thread) => {
|
|
12424
|
+
const threadId = started[thread.key];
|
|
12425
|
+
const tried = startOf(thread.key);
|
|
12426
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { "data-testid": "proposed-thread", children: [
|
|
12427
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "break-words font-medium text-foreground", children: /* @__PURE__ */ jsxRuntime.jsx("bdi", { children: thread.title }) }),
|
|
12428
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 break-words text-foreground/70", children: /* @__PURE__ */ jsxRuntime.jsx("bdi", { children: thread.goal }) }),
|
|
12429
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
12430
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { role: "status", children: threadId ? "Started" : "" }),
|
|
12431
|
+
threadId ? onOpenSession && /* @__PURE__ */ jsxRuntime.jsx(
|
|
12432
|
+
Button,
|
|
12433
|
+
{
|
|
12434
|
+
ref: (node) => {
|
|
12435
|
+
viewButtons.current[thread.key] = node;
|
|
12436
|
+
},
|
|
12437
|
+
size: "xs",
|
|
12438
|
+
variant: "ghost",
|
|
12439
|
+
"aria-label": `View thread ${thread.title}`,
|
|
12440
|
+
onClick: () => onOpenSession(threadId),
|
|
12441
|
+
children: "View thread"
|
|
12442
|
+
}
|
|
12443
|
+
) : thread.where === "device" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
12444
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: canStartHere ? "Allow Surogate to work in a folder on your device" : "This works in a folder on your device, which needs Surogate Desktop" }),
|
|
12445
|
+
canStartHere && /* @__PURE__ */ jsxRuntime.jsx(
|
|
12446
|
+
Button,
|
|
12447
|
+
{
|
|
12448
|
+
size: "xs",
|
|
12449
|
+
variant: "outline",
|
|
12450
|
+
disabled: starting(thread.key),
|
|
12451
|
+
"aria-label": `${startingAt(thread.key) === "device" ? "Starting" : "Allow"} ${thread.title}`,
|
|
12452
|
+
onClick: () => void start(thread.key, thread.title),
|
|
12453
|
+
children: startingAt(thread.key) === "device" ? "Starting\u2026" : "Allow"
|
|
12454
|
+
}
|
|
12455
|
+
),
|
|
12456
|
+
canStart && /* @__PURE__ */ jsxRuntime.jsx(
|
|
12457
|
+
Button,
|
|
12458
|
+
{
|
|
12459
|
+
size: "xs",
|
|
12460
|
+
variant: "ghost",
|
|
12461
|
+
disabled: starting(thread.key),
|
|
12462
|
+
"aria-label": startingAt(thread.key) === "cloud" ? `Starting ${thread.title}` : `Run ${thread.title} in the cloud instead`,
|
|
12463
|
+
onClick: () => void start(thread.key),
|
|
12464
|
+
children: startingAt(thread.key) === "cloud" ? "Starting\u2026" : "Run in the cloud instead"
|
|
12465
|
+
}
|
|
12466
|
+
)
|
|
12467
|
+
] }) : canStart ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
12468
|
+
Button,
|
|
12469
|
+
{
|
|
12470
|
+
size: "xs",
|
|
12471
|
+
variant: "outline",
|
|
12472
|
+
disabled: starting(thread.key),
|
|
12473
|
+
"aria-label": `${starting(thread.key) ? "Starting" : "Start"} ${thread.title}`,
|
|
12474
|
+
onClick: () => void start(thread.key),
|
|
12475
|
+
children: starting(thread.key) ? "Starting\u2026" : "Start"
|
|
12476
|
+
}
|
|
12477
|
+
) : null
|
|
12478
|
+
] }),
|
|
12479
|
+
!threadId && tried?.state === "failed" && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-xs text-destructive", children: tried.error })
|
|
12480
|
+
] }, thread.key);
|
|
12481
|
+
}) })
|
|
12482
|
+
] });
|
|
12483
|
+
}
|
|
12048
12484
|
|
|
12049
12485
|
// src/components/chat/chat-thread.tsx
|
|
12050
12486
|
init_utils();
|
|
@@ -12103,6 +12539,9 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
|
|
|
12103
12539
|
originatingSessionId
|
|
12104
12540
|
}];
|
|
12105
12541
|
}
|
|
12542
|
+
if (msg.systemKind === "worker" || msg.systemKind === "thread_proposal") {
|
|
12543
|
+
return [{ kind: "thread_card", key: msg.id, message: msg }];
|
|
12544
|
+
}
|
|
12106
12545
|
if (msg.systemKind === "browser_marker" || msg.systemKind === "browser_marker_warning") {
|
|
12107
12546
|
return [{
|
|
12108
12547
|
kind: "browser_marker",
|
|
@@ -12334,6 +12773,9 @@ function OrphanSystemMarker({
|
|
|
12334
12773
|
if (message.systemKind === "error" && message.errorInfo) {
|
|
12335
12774
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto my-2 w-full max-w-4xl", children: /* @__PURE__ */ jsxRuntime.jsx(ErrorMessage, { errorInfo: message.errorInfo, onRetry }) });
|
|
12336
12775
|
}
|
|
12776
|
+
if (message.systemKind === "worker" || message.systemKind === "thread_proposal") {
|
|
12777
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full max-w-4xl px-4", children: /* @__PURE__ */ jsxRuntime.jsx(ThreadCards, { message }) });
|
|
12778
|
+
}
|
|
12337
12779
|
return null;
|
|
12338
12780
|
}
|
|
12339
12781
|
function CancelledToolRow({ tc }) {
|
|
@@ -12438,6 +12880,15 @@ function TimelineEntryItem({
|
|
|
12438
12880
|
) })
|
|
12439
12881
|
] });
|
|
12440
12882
|
}
|
|
12883
|
+
if (entry.kind === "thread_card") {
|
|
12884
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
|
|
12885
|
+
/* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
|
|
12886
|
+
/* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
|
|
12887
|
+
/* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-foreground/30" })
|
|
12888
|
+
] }),
|
|
12889
|
+
/* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx(ThreadCards, { message: entry.message }) })
|
|
12890
|
+
] });
|
|
12891
|
+
}
|
|
12441
12892
|
if (entry.kind === "skill_invoked") {
|
|
12442
12893
|
return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
|
|
12443
12894
|
/* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
|
|
@@ -13050,7 +13501,9 @@ function ChatThread({
|
|
|
13050
13501
|
researchSources = [],
|
|
13051
13502
|
hideTurnSummary = false,
|
|
13052
13503
|
agentId,
|
|
13053
|
-
onOpenIntegrations
|
|
13504
|
+
onOpenIntegrations,
|
|
13505
|
+
deviceWait = null,
|
|
13506
|
+
composerFooter
|
|
13054
13507
|
}) {
|
|
13055
13508
|
const groups = react.useMemo(() => groupMessages(messages), [messages]);
|
|
13056
13509
|
const activeAssistantId = react.useMemo(() => {
|
|
@@ -13126,6 +13579,18 @@ function ChatThread({
|
|
|
13126
13579
|
return null;
|
|
13127
13580
|
}, [messages]);
|
|
13128
13581
|
const composerArea = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
|
|
13582
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13583
|
+
"div",
|
|
13584
|
+
{
|
|
13585
|
+
role: "status",
|
|
13586
|
+
"data-testid": "device-wait",
|
|
13587
|
+
className: deviceWait ? "mb-2 flex items-center gap-2 rounded-lg border border-line bg-card px-3 py-2 text-sm text-muted-foreground" : void 0,
|
|
13588
|
+
children: deviceWait && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
13589
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.LaptopIcon, { className: "size-4 shrink-0", "aria-hidden": "true" }),
|
|
13590
|
+
deviceWait.reason === "copying" ? `Making this thread's copy of ${deviceWait.folder || "its folder"}\u2026` : `Waiting for ${deviceWait.deviceName || "your computer"}. The chat goes on once it is back online.`
|
|
13591
|
+
] })
|
|
13592
|
+
}
|
|
13593
|
+
),
|
|
13129
13594
|
retryIndicator && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(RetryBanner, { indicator: retryIndicator }) }),
|
|
13130
13595
|
researchSources.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(ResearchSourcesPanel, { sources: researchSources }) }),
|
|
13131
13596
|
(paneCards?.browser || paneCards?.files) && // Layers sliding out from behind the prompt box: each card is
|
|
@@ -13210,6 +13675,7 @@ function ChatThread({
|
|
|
13210
13675
|
compressEnabled
|
|
13211
13676
|
}
|
|
13212
13677
|
),
|
|
13678
|
+
composerFooter,
|
|
13213
13679
|
onOpenIntegrations && /* @__PURE__ */ jsxRuntime.jsx(
|
|
13214
13680
|
IntegrationsBandSlot,
|
|
13215
13681
|
{
|
|
@@ -13224,76 +13690,80 @@ function ChatThread({
|
|
|
13224
13690
|
// input floating mid-screen with dead space under it, and the keyboard
|
|
13225
13691
|
// then opens across that gap. There the composer keeps its usual place
|
|
13226
13692
|
// at the bottom edge and the greeting takes the space above it.
|
|
13227
|
-
|
|
13693
|
+
// data-transcript: the host's transcript settings shape this, the composer with it.
|
|
13694
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { "data-transcript": "", className: "flex flex-1 flex-col items-center justify-end overflow-hidden bg-background px-3 text-base md:justify-center", children: [
|
|
13228
13695
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex w-full flex-1 flex-col justify-center md:flex-none", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "mb-6 text-center text-2xl font-bold text-foreground md:mb-8", children: "What should we do ?" }) }),
|
|
13229
13696
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", children: composerArea })
|
|
13230
13697
|
] })
|
|
13231
13698
|
);
|
|
13232
13699
|
}
|
|
13233
|
-
return
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
{
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
groups.map((group) => {
|
|
13244
|
-
if (group.role === "user") {
|
|
13245
|
-
const msg = group.messages[0];
|
|
13246
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
13247
|
-
ChatMessage,
|
|
13248
|
-
{
|
|
13249
|
-
message: msg,
|
|
13250
|
-
onFileSelect
|
|
13251
|
-
},
|
|
13252
|
-
msg.id
|
|
13253
|
-
);
|
|
13700
|
+
return (
|
|
13701
|
+
// data-transcript: the host's transcript settings shape this, the composer with it.
|
|
13702
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { "data-transcript": "", className: "flex flex-1 flex-col overflow-hidden bg-background text-base", children: [
|
|
13703
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Conversation, { className: "relative flex-1 min-h-0", children: [
|
|
13704
|
+
/* @__PURE__ */ jsxRuntime.jsx(ConversationContent, { className: "mx-auto w-full max-w-4xl", children: messages.length === 0 && isLoadingHistory ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
13705
|
+
ConversationEmptyState,
|
|
13706
|
+
{
|
|
13707
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquareIcon, { className: "size-8 opacity-40" }),
|
|
13708
|
+
title: "Loading conversation",
|
|
13709
|
+
description: "Fetching the session history."
|
|
13254
13710
|
}
|
|
13255
|
-
|
|
13256
|
-
|
|
13257
|
-
if (
|
|
13258
|
-
|
|
13711
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
13712
|
+
groups.map((group) => {
|
|
13713
|
+
if (group.role === "user") {
|
|
13714
|
+
const msg = group.messages[0];
|
|
13715
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
13716
|
+
ChatMessage,
|
|
13717
|
+
{
|
|
13718
|
+
message: msg,
|
|
13719
|
+
onFileSelect
|
|
13720
|
+
},
|
|
13721
|
+
msg.id
|
|
13722
|
+
);
|
|
13723
|
+
}
|
|
13724
|
+
if (group.role === "system") {
|
|
13725
|
+
const msg = group.messages[0];
|
|
13726
|
+
if (viewMode === "simple" && msg.systemKind === "skill_invoked") {
|
|
13727
|
+
return null;
|
|
13728
|
+
}
|
|
13729
|
+
const groupRetry2 = msg.id === activeFailureId ? onRetry : void 0;
|
|
13730
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
13731
|
+
OrphanSystemMarker,
|
|
13732
|
+
{
|
|
13733
|
+
message: msg,
|
|
13734
|
+
sessionId,
|
|
13735
|
+
onRetry: groupRetry2
|
|
13736
|
+
},
|
|
13737
|
+
msg.id
|
|
13738
|
+
);
|
|
13259
13739
|
}
|
|
13260
|
-
const
|
|
13740
|
+
const groupTail = group.messages[group.messages.length - 1];
|
|
13741
|
+
const groupRetry = groupTail.id === activeFailureId ? onRetry : void 0;
|
|
13261
13742
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
13262
|
-
|
|
13743
|
+
AssistantGroup,
|
|
13263
13744
|
{
|
|
13264
|
-
|
|
13745
|
+
messages: group.messages,
|
|
13746
|
+
activeAssistantId,
|
|
13747
|
+
lastGlobalIndex: group.lastGlobalIndex,
|
|
13748
|
+
totalMessages: messages.length,
|
|
13749
|
+
isRunning,
|
|
13265
13750
|
sessionId,
|
|
13266
|
-
|
|
13751
|
+
artifactFallbacks,
|
|
13752
|
+
onFileSelect,
|
|
13753
|
+
onRetry: groupRetry,
|
|
13754
|
+
viewMode,
|
|
13755
|
+
hideTurnSummary
|
|
13267
13756
|
},
|
|
13268
|
-
|
|
13757
|
+
group.messages[0].id
|
|
13269
13758
|
);
|
|
13270
|
-
}
|
|
13271
|
-
|
|
13272
|
-
|
|
13273
|
-
|
|
13274
|
-
|
|
13275
|
-
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
lastGlobalIndex: group.lastGlobalIndex,
|
|
13279
|
-
totalMessages: messages.length,
|
|
13280
|
-
isRunning,
|
|
13281
|
-
sessionId,
|
|
13282
|
-
artifactFallbacks,
|
|
13283
|
-
onFileSelect,
|
|
13284
|
-
onRetry: groupRetry,
|
|
13285
|
-
viewMode,
|
|
13286
|
-
hideTurnSummary
|
|
13287
|
-
},
|
|
13288
|
-
group.messages[0].id
|
|
13289
|
-
);
|
|
13290
|
-
}),
|
|
13291
|
-
showWorkingOnIt && /* @__PURE__ */ jsxRuntime.jsx(WorkingOnItIndicator, { activity: orbActivity })
|
|
13292
|
-
] }) }),
|
|
13293
|
-
/* @__PURE__ */ jsxRuntime.jsx(ConversationScrollButton, {})
|
|
13294
|
-
] }),
|
|
13295
|
-
composerArea
|
|
13296
|
-
] });
|
|
13759
|
+
}),
|
|
13760
|
+
showWorkingOnIt && /* @__PURE__ */ jsxRuntime.jsx(WorkingOnItIndicator, { activity: orbActivity })
|
|
13761
|
+
] }) }),
|
|
13762
|
+
/* @__PURE__ */ jsxRuntime.jsx(ConversationScrollButton, {})
|
|
13763
|
+
] }),
|
|
13764
|
+
composerArea
|
|
13765
|
+
] })
|
|
13766
|
+
);
|
|
13297
13767
|
}
|
|
13298
13768
|
function IntegrationsBandSlot({
|
|
13299
13769
|
agentId,
|
|
@@ -13323,6 +13793,7 @@ var WORKSPACE_MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
|
13323
13793
|
]);
|
|
13324
13794
|
var AGENT_CHAT_LISTENED_EVENTS = [
|
|
13325
13795
|
"user.message",
|
|
13796
|
+
"coordinator.message",
|
|
13326
13797
|
"llm.request",
|
|
13327
13798
|
"llm.response",
|
|
13328
13799
|
"llm.thinking",
|
|
@@ -13354,61 +13825,284 @@ var AGENT_CHAT_LISTENED_EVENTS = [
|
|
|
13354
13825
|
"artifact.updated",
|
|
13355
13826
|
"browser.provisioned",
|
|
13356
13827
|
"browser.destroyed",
|
|
13828
|
+
"browser.unavailable",
|
|
13357
13829
|
"browser.control_granted",
|
|
13358
13830
|
"browser.control_returned",
|
|
13359
13831
|
"ask_user_question.response",
|
|
13360
13832
|
"iteration.summary",
|
|
13361
13833
|
"turn.summary",
|
|
13362
|
-
"loop.result"
|
|
13834
|
+
"loop.result",
|
|
13835
|
+
"device.waiting",
|
|
13836
|
+
"device.resumed",
|
|
13837
|
+
"worker.spawned",
|
|
13838
|
+
"worker.complete",
|
|
13839
|
+
"worker.failed",
|
|
13840
|
+
"thread.proposed"
|
|
13363
13841
|
];
|
|
13364
13842
|
var AGENT_CHAT_LISTENED_EVENT_SET = new Set(
|
|
13365
13843
|
AGENT_CHAT_LISTENED_EVENTS
|
|
13366
13844
|
);
|
|
13367
13845
|
|
|
13368
|
-
// src/
|
|
13369
|
-
var
|
|
13370
|
-
|
|
13371
|
-
|
|
13372
|
-
reasoningTokens: 0,
|
|
13373
|
-
cachedInputTokens: 0,
|
|
13374
|
-
totalTokens: 0,
|
|
13375
|
-
contextWindow: 0,
|
|
13376
|
-
model: ""
|
|
13377
|
-
};
|
|
13378
|
-
function createInitialAgentChatState(options = {}) {
|
|
13379
|
-
return {
|
|
13380
|
-
messages: [],
|
|
13381
|
-
isRunning: false,
|
|
13382
|
-
isLoadingHistory: options.isLoadingHistory ?? false,
|
|
13383
|
-
tokenUsage: EMPTY_TOKEN_USAGE,
|
|
13384
|
-
retryIndicator: null,
|
|
13385
|
-
lastEventId: 0,
|
|
13386
|
-
sessionDone: false,
|
|
13387
|
-
hadDeltas: false,
|
|
13388
|
-
terminal: false,
|
|
13389
|
-
stopped: false,
|
|
13390
|
-
workspaceRefreshKey: 0,
|
|
13391
|
-
browser: null,
|
|
13392
|
-
viewMode: options.viewMode ?? "simple",
|
|
13393
|
-
researchSources: []
|
|
13394
|
-
};
|
|
13846
|
+
// src/components/missions/mission-derive.ts
|
|
13847
|
+
var ACTIVE_MISSION_STATUSES = /* @__PURE__ */ new Set(["active", "paused"]);
|
|
13848
|
+
function isTerminalMissionStatus(status) {
|
|
13849
|
+
return !ACTIVE_MISSION_STATUSES.has(status);
|
|
13395
13850
|
}
|
|
13396
|
-
function
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13401
|
-
|
|
13851
|
+
function missionTaskBucket(status) {
|
|
13852
|
+
if (status === "done") return "done";
|
|
13853
|
+
if (status === "blocked") return "blocked";
|
|
13854
|
+
if (status === "failed" || status === "cancelled") {
|
|
13855
|
+
return "failed_or_cancelled";
|
|
13856
|
+
}
|
|
13857
|
+
return "in_flight";
|
|
13858
|
+
}
|
|
13859
|
+
function groupMissionTasksByBucket(tasks) {
|
|
13860
|
+
const buckets = {
|
|
13861
|
+
in_flight: [],
|
|
13862
|
+
done: [],
|
|
13863
|
+
blocked: [],
|
|
13864
|
+
failed_or_cancelled: []
|
|
13402
13865
|
};
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
return nextState;
|
|
13866
|
+
for (const t of tasks) {
|
|
13867
|
+
buckets[missionTaskBucket(t.status)].push(t);
|
|
13406
13868
|
}
|
|
13407
|
-
|
|
13408
|
-
|
|
13409
|
-
|
|
13410
|
-
|
|
13411
|
-
|
|
13869
|
+
return buckets;
|
|
13870
|
+
}
|
|
13871
|
+
function deriveMissionWorkerActivityLabel(worker) {
|
|
13872
|
+
const kind = worker.latestEventKind;
|
|
13873
|
+
if (kind === "tool.call" && worker.latestEventSummary) {
|
|
13874
|
+
return `tool: ${truncate4(worker.latestEventSummary, 80)}`;
|
|
13875
|
+
}
|
|
13876
|
+
if (kind === "llm.response") {
|
|
13877
|
+
return "thinking";
|
|
13878
|
+
}
|
|
13879
|
+
if (kind === "tool.result") {
|
|
13880
|
+
return "received tool result";
|
|
13881
|
+
}
|
|
13882
|
+
if (kind && worker.latestEventSummary) {
|
|
13883
|
+
return `${kind}: ${truncate4(worker.latestEventSummary, 60)}`;
|
|
13884
|
+
}
|
|
13885
|
+
return worker.sessionStatus || "active";
|
|
13886
|
+
}
|
|
13887
|
+
function truncate4(s, max) {
|
|
13888
|
+
if (s.length <= max) return s;
|
|
13889
|
+
return `${s.slice(0, max - 1)}\u2026`;
|
|
13890
|
+
}
|
|
13891
|
+
var MISSION_EVENT_CATEGORIES = [
|
|
13892
|
+
"spawn",
|
|
13893
|
+
"output",
|
|
13894
|
+
"done",
|
|
13895
|
+
"verdict",
|
|
13896
|
+
"system"
|
|
13897
|
+
];
|
|
13898
|
+
function missionEventCategory(type) {
|
|
13899
|
+
switch (type) {
|
|
13900
|
+
case "worker.spawned":
|
|
13901
|
+
return "spawn";
|
|
13902
|
+
case "iteration.summary":
|
|
13903
|
+
return "output";
|
|
13904
|
+
case "worker.complete":
|
|
13905
|
+
return "done";
|
|
13906
|
+
case "mission.evaluation.end":
|
|
13907
|
+
return "verdict";
|
|
13908
|
+
default:
|
|
13909
|
+
return "system";
|
|
13910
|
+
}
|
|
13911
|
+
}
|
|
13912
|
+
function dataString(data, key) {
|
|
13913
|
+
const v = data?.[key];
|
|
13914
|
+
return typeof v === "string" && v.trim().length > 0 ? v.trim() : null;
|
|
13915
|
+
}
|
|
13916
|
+
var CONTROL_MARKUP_RE = /<next_action\b[^>]*(?:\/>|>[\s\S]*?<\/next_action>)/gi;
|
|
13917
|
+
function stripMissionControlMarkup(text) {
|
|
13918
|
+
return text.replace(CONTROL_MARKUP_RE, "").trim();
|
|
13919
|
+
}
|
|
13920
|
+
function missionEventSummary(event) {
|
|
13921
|
+
return stripMissionControlMarkup(rawMissionEventSummary(event));
|
|
13922
|
+
}
|
|
13923
|
+
function rawMissionEventSummary(event) {
|
|
13924
|
+
const d = event.data ?? null;
|
|
13925
|
+
switch (event.type) {
|
|
13926
|
+
case "worker.spawned":
|
|
13927
|
+
return dataString(d, "goal") ?? "Task spawned";
|
|
13928
|
+
case "worker.complete":
|
|
13929
|
+
return dataString(d, "result") ?? "Worker complete";
|
|
13930
|
+
case "iteration.summary":
|
|
13931
|
+
return dataString(d, "summary") ?? "";
|
|
13932
|
+
case "mission.evaluation.end": {
|
|
13933
|
+
const result = dataString(d, "result");
|
|
13934
|
+
const feedback = dataString(d, "feedback") ?? dataString(d, "explanation");
|
|
13935
|
+
if (result && feedback) return `${result} \u2014 ${feedback}`;
|
|
13936
|
+
return result ?? feedback ?? "Evaluation finished";
|
|
13937
|
+
}
|
|
13938
|
+
case "task.blocked":
|
|
13939
|
+
return dataString(d, "reason") ?? "Task blocked";
|
|
13940
|
+
case "task.failed":
|
|
13941
|
+
return "Task failed";
|
|
13942
|
+
default: {
|
|
13943
|
+
const text = dataString(d, "summary") ?? dataString(d, "text") ?? dataString(d, "message") ?? dataString(d, "reason");
|
|
13944
|
+
if (text) return text;
|
|
13945
|
+
if (!d) return "";
|
|
13946
|
+
try {
|
|
13947
|
+
return truncate4(JSON.stringify(d), 160);
|
|
13948
|
+
} catch {
|
|
13949
|
+
return "";
|
|
13950
|
+
}
|
|
13951
|
+
}
|
|
13952
|
+
}
|
|
13953
|
+
}
|
|
13954
|
+
function missionEventTaskId(event, sessions) {
|
|
13955
|
+
return dataString(event.data ?? null, "task_id") ?? sessions[event.sessionId]?.taskId ?? null;
|
|
13956
|
+
}
|
|
13957
|
+
function missionEventActorLabel(event, sessions) {
|
|
13958
|
+
const meta = sessions[event.sessionId];
|
|
13959
|
+
if (!meta) return event.sessionId.slice(0, 8);
|
|
13960
|
+
if (meta.kind === "coordinator") return "orchestrator";
|
|
13961
|
+
return meta.agentDefName ?? "worker";
|
|
13962
|
+
}
|
|
13963
|
+
function missionTaskBlocks(tasks) {
|
|
13964
|
+
const blocks = {};
|
|
13965
|
+
for (const t of tasks) {
|
|
13966
|
+
for (const parent of t.parentIds) {
|
|
13967
|
+
(blocks[parent] ?? (blocks[parent] = [])).push(t.id);
|
|
13968
|
+
}
|
|
13969
|
+
}
|
|
13970
|
+
return blocks;
|
|
13971
|
+
}
|
|
13972
|
+
function missionTaskTitle(goal, max = 80) {
|
|
13973
|
+
const line = goal.split("\n").find((l) => l.trim().length > 0)?.trim() ?? goal.trim();
|
|
13974
|
+
return truncate4(line.replace(/\s+/g, " "), max);
|
|
13975
|
+
}
|
|
13976
|
+
var RAIL_GROUP_DEFS = [
|
|
13977
|
+
{ key: "running", label: "Running", match: (s) => s === "running" },
|
|
13978
|
+
{ key: "blocked", label: "Blocked", match: (s) => s === "blocked" },
|
|
13979
|
+
{
|
|
13980
|
+
key: "queued",
|
|
13981
|
+
label: "Queued",
|
|
13982
|
+
match: (s) => s === "ready" || s === "todo"
|
|
13983
|
+
},
|
|
13984
|
+
{ key: "done", label: "Done", match: (s) => s === "done" },
|
|
13985
|
+
{
|
|
13986
|
+
key: "failed",
|
|
13987
|
+
label: "Failed / cancelled",
|
|
13988
|
+
match: (s) => s === "failed" || s === "cancelled"
|
|
13989
|
+
}
|
|
13990
|
+
];
|
|
13991
|
+
function missionTaskRailGroups(tasks) {
|
|
13992
|
+
const grouped = /* @__PURE__ */ new Set();
|
|
13993
|
+
const groups = RAIL_GROUP_DEFS.map((def) => ({
|
|
13994
|
+
key: def.key,
|
|
13995
|
+
label: def.label,
|
|
13996
|
+
tasks: tasks.filter((t) => {
|
|
13997
|
+
const hit = def.match(t.status);
|
|
13998
|
+
if (hit) grouped.add(t.id);
|
|
13999
|
+
return hit;
|
|
14000
|
+
})
|
|
14001
|
+
}));
|
|
14002
|
+
const stray = tasks.filter((t) => !grouped.has(t.id));
|
|
14003
|
+
if (stray.length > 0) {
|
|
14004
|
+
const queued = groups.find((g) => g.key === "queued");
|
|
14005
|
+
if (queued) queued.tasks.push(...stray);
|
|
14006
|
+
}
|
|
14007
|
+
return groups.filter((g) => g.tasks.length > 0);
|
|
14008
|
+
}
|
|
14009
|
+
var TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
14010
|
+
function defaultSelectedMissionTaskId(tasks) {
|
|
14011
|
+
if (tasks.length === 0) return null;
|
|
14012
|
+
const ordered = missionTaskRailGroups(tasks).flatMap((g) => g.tasks);
|
|
14013
|
+
const firstLive = ordered.find(
|
|
14014
|
+
(t) => !TERMINAL_TASK_STATUSES.has(t.status)
|
|
14015
|
+
);
|
|
14016
|
+
return (firstLive ?? ordered[0] ?? tasks[0]).id;
|
|
14017
|
+
}
|
|
14018
|
+
function mergeMissionEvents(existing, incoming) {
|
|
14019
|
+
if (incoming.length === 0) return existing;
|
|
14020
|
+
const byId = /* @__PURE__ */ new Map();
|
|
14021
|
+
for (const e of existing) byId.set(e.id, e);
|
|
14022
|
+
for (const e of incoming) byId.set(e.id, e);
|
|
14023
|
+
return [...byId.values()].sort((a, b) => a.id - b.id);
|
|
14024
|
+
}
|
|
14025
|
+
function missionTaskStatusDotClass(status) {
|
|
14026
|
+
switch (status) {
|
|
14027
|
+
case "running":
|
|
14028
|
+
return "bg-primary";
|
|
14029
|
+
case "blocked":
|
|
14030
|
+
return "bg-amber-500";
|
|
14031
|
+
case "done":
|
|
14032
|
+
return "bg-emerald-500";
|
|
14033
|
+
case "failed":
|
|
14034
|
+
return "bg-destructive";
|
|
14035
|
+
case "cancelled":
|
|
14036
|
+
return "bg-muted-foreground";
|
|
14037
|
+
default:
|
|
14038
|
+
return "bg-foreground/30";
|
|
14039
|
+
}
|
|
14040
|
+
}
|
|
14041
|
+
function missionWorkerTaskCounts(worker, tasks) {
|
|
14042
|
+
if (worker.kind !== "task") return null;
|
|
14043
|
+
const mine = tasks.filter((t) => t.agentDefName === worker.agentDefName);
|
|
14044
|
+
return {
|
|
14045
|
+
completed: mine.filter((t) => t.status === "done").length,
|
|
14046
|
+
inFlight: mine.filter(
|
|
14047
|
+
(t) => t.status === "running" || t.status === "blocked"
|
|
14048
|
+
).length
|
|
14049
|
+
};
|
|
14050
|
+
}
|
|
14051
|
+
function formatMissionTimestamp(iso) {
|
|
14052
|
+
if (!iso) return "";
|
|
14053
|
+
const parsed = new Date(iso);
|
|
14054
|
+
if (Number.isNaN(parsed.getTime())) return iso;
|
|
14055
|
+
return parsed.toLocaleTimeString();
|
|
14056
|
+
}
|
|
14057
|
+
|
|
14058
|
+
// src/runtime/reducer.ts
|
|
14059
|
+
var EMPTY_TOKEN_USAGE = {
|
|
14060
|
+
inputTokens: 0,
|
|
14061
|
+
outputTokens: 0,
|
|
14062
|
+
reasoningTokens: 0,
|
|
14063
|
+
cachedInputTokens: 0,
|
|
14064
|
+
totalTokens: 0,
|
|
14065
|
+
contextWindow: 0,
|
|
14066
|
+
model: ""
|
|
14067
|
+
};
|
|
14068
|
+
function createInitialAgentChatState(options = {}) {
|
|
14069
|
+
return {
|
|
14070
|
+
messages: [],
|
|
14071
|
+
isRunning: false,
|
|
14072
|
+
isLoadingHistory: options.isLoadingHistory ?? false,
|
|
14073
|
+
tokenUsage: EMPTY_TOKEN_USAGE,
|
|
14074
|
+
retryIndicator: null,
|
|
14075
|
+
lastEventId: 0,
|
|
14076
|
+
sessionDone: false,
|
|
14077
|
+
hadDeltas: false,
|
|
14078
|
+
terminal: false,
|
|
14079
|
+
stopped: false,
|
|
14080
|
+
workspaceRefreshKey: 0,
|
|
14081
|
+
browser: null,
|
|
14082
|
+
browserTabs: [],
|
|
14083
|
+
viewMode: options.viewMode ?? "simple",
|
|
14084
|
+
researchSources: [],
|
|
14085
|
+
deviceWait: null
|
|
14086
|
+
};
|
|
14087
|
+
}
|
|
14088
|
+
function applyAgentChatEvent(state, event) {
|
|
14089
|
+
let nextState = {
|
|
14090
|
+
...state,
|
|
14091
|
+
isLoadingHistory: false,
|
|
14092
|
+
lastEventId: Math.max(state.lastEventId, event.eventId),
|
|
14093
|
+
sessionDone: state.sessionDone || event.type === "session.done"
|
|
14094
|
+
};
|
|
14095
|
+
nextState = applyRetryIndicator(nextState, event);
|
|
14096
|
+
if (state.stopped && isPostTerminalLiveEvent(event.type)) {
|
|
14097
|
+
return nextState;
|
|
14098
|
+
}
|
|
14099
|
+
switch (event.type) {
|
|
14100
|
+
case "user.message":
|
|
14101
|
+
// A project coordinator's follow-up to its thread: shown as the thread reads it, its header saying whose it is.
|
|
14102
|
+
case "coordinator.message":
|
|
14103
|
+
return withMessages(
|
|
14104
|
+
{ ...nextState, terminal: false, stopped: false, isRunning: true },
|
|
14105
|
+
applyUserMessage(nextState.messages, event.eventId, event.data)
|
|
13412
14106
|
);
|
|
13413
14107
|
case "skill.invoked":
|
|
13414
14108
|
return withMessages(nextState, [
|
|
@@ -13482,23 +14176,79 @@ function applyAgentChatEvent(state, event) {
|
|
|
13482
14176
|
}
|
|
13483
14177
|
return withMessages(nextState, [...nextState.messages, artifactMessage]);
|
|
13484
14178
|
}
|
|
13485
|
-
case "browser.provisioned":
|
|
13486
|
-
|
|
14179
|
+
case "browser.provisioned": {
|
|
14180
|
+
const tab = computerTab(event);
|
|
14181
|
+
if (tab === null) {
|
|
14182
|
+
return applyBrowserEvent(nextState, event, {
|
|
14183
|
+
status: "live",
|
|
14184
|
+
controlOwner: null
|
|
14185
|
+
});
|
|
14186
|
+
}
|
|
14187
|
+
const browserTabs = [...nextState.browserTabs.filter((open) => open !== tab), tab];
|
|
14188
|
+
if (nextState.browserTabs.length > 0) {
|
|
14189
|
+
const there = nextState.browser?.computer === true && nextState.browser.status !== "unavailable";
|
|
14190
|
+
return {
|
|
14191
|
+
...nextState,
|
|
14192
|
+
browserTabs,
|
|
14193
|
+
browser: there ? nextState.browser : { status: "live", controlOwner: null, computer: true }
|
|
14194
|
+
};
|
|
14195
|
+
}
|
|
14196
|
+
return applyBrowserEvent({ ...nextState, browserTabs }, event, {
|
|
13487
14197
|
status: "live",
|
|
13488
|
-
controlOwner: null
|
|
14198
|
+
controlOwner: null,
|
|
14199
|
+
computer: true
|
|
13489
14200
|
});
|
|
14201
|
+
}
|
|
14202
|
+
case "browser.unavailable": {
|
|
14203
|
+
const tab = computerTab(event);
|
|
14204
|
+
const browserTabs = tab === null ? [] : nextState.browserTabs.filter((open) => open !== tab);
|
|
14205
|
+
if (browserTabs.length > 0) return { ...nextState, browserTabs };
|
|
14206
|
+
const none = {
|
|
14207
|
+
...nextState,
|
|
14208
|
+
browserTabs,
|
|
14209
|
+
browser: { status: "unavailable", controlOwner: null, computer: true }
|
|
14210
|
+
};
|
|
14211
|
+
if (saidLastOfBrowser(nextState.messages) === NO_BROWSER) return none;
|
|
14212
|
+
return withMessages(none, [...none.messages, browserMarker(event)]);
|
|
14213
|
+
}
|
|
14214
|
+
// A take-over on the user's computer is told as the cloud's is: the browser stays the computer's.
|
|
13490
14215
|
case "browser.control_granted":
|
|
13491
14216
|
return applyBrowserEvent(nextState, event, {
|
|
13492
14217
|
status: "user-control",
|
|
13493
|
-
controlOwner: stringValue2(event.data.owner_user_id) || null
|
|
14218
|
+
controlOwner: stringValue2(event.data.owner_user_id) || null,
|
|
14219
|
+
...onComputer(event)
|
|
13494
14220
|
});
|
|
13495
14221
|
case "browser.control_returned":
|
|
13496
14222
|
return applyBrowserEvent(nextState, event, {
|
|
13497
14223
|
status: "live",
|
|
13498
|
-
controlOwner: null
|
|
14224
|
+
controlOwner: null,
|
|
14225
|
+
...onComputer(event)
|
|
13499
14226
|
});
|
|
13500
|
-
case "browser.destroyed":
|
|
13501
|
-
|
|
14227
|
+
case "browser.destroyed": {
|
|
14228
|
+
const tab = computerTab(event);
|
|
14229
|
+
const browserTabs = tab === null ? [] : nextState.browserTabs.filter((open) => open !== tab);
|
|
14230
|
+
if (browserTabs.length > 0) return { ...nextState, browserTabs };
|
|
14231
|
+
return applyBrowserEvent({ ...nextState, browserTabs }, event, null);
|
|
14232
|
+
}
|
|
14233
|
+
case "device.waiting":
|
|
14234
|
+
return {
|
|
14235
|
+
...nextState,
|
|
14236
|
+
deviceWait: {
|
|
14237
|
+
deviceId: stringValue2(event.data.device_id),
|
|
14238
|
+
deviceName: stringValue2(event.data.device_name),
|
|
14239
|
+
reason: stringValue2(event.data.reason) || "offline",
|
|
14240
|
+
folder: stringValue2(event.data.folder)
|
|
14241
|
+
}
|
|
14242
|
+
};
|
|
14243
|
+
case "device.resumed":
|
|
14244
|
+
return { ...nextState, deviceWait: null };
|
|
14245
|
+
case "worker.spawned":
|
|
14246
|
+
return withMessages(nextState, applyWorkerSpawned(nextState.messages, event));
|
|
14247
|
+
case "worker.complete":
|
|
14248
|
+
case "worker.failed":
|
|
14249
|
+
return withMessages(nextState, applyWorkerReport(nextState.messages, event));
|
|
14250
|
+
case "thread.proposed":
|
|
14251
|
+
return withMessages(nextState, [...nextState.messages, proposalMessage(event)]);
|
|
13502
14252
|
case "llm.delta":
|
|
13503
14253
|
return applyLlmDelta(nextState, event);
|
|
13504
14254
|
case "llm.response":
|
|
@@ -13520,7 +14270,7 @@ function applyAgentChatEvent(state, event) {
|
|
|
13520
14270
|
return collectResearchSource(withResult, toolName, event.data);
|
|
13521
14271
|
}
|
|
13522
14272
|
case "harness.wake":
|
|
13523
|
-
return { ...nextState, terminal: false, isRunning: true };
|
|
14273
|
+
return { ...nextState, terminal: false, isRunning: true, deviceWait: null };
|
|
13524
14274
|
case "llm.request":
|
|
13525
14275
|
return {
|
|
13526
14276
|
...nextState,
|
|
@@ -13549,7 +14299,7 @@ function applyAgentChatEvent(state, event) {
|
|
|
13549
14299
|
if (event.data.strategy === "clear") {
|
|
13550
14300
|
return {
|
|
13551
14301
|
...nextState,
|
|
13552
|
-
messages:
|
|
14302
|
+
messages: typedBehindTheClear(nextState.messages, event.data.answers),
|
|
13553
14303
|
tokenUsage: EMPTY_TOKEN_USAGE
|
|
13554
14304
|
};
|
|
13555
14305
|
}
|
|
@@ -13641,6 +14391,17 @@ function applyRetryIndicator(state, event) {
|
|
|
13641
14391
|
return state;
|
|
13642
14392
|
}
|
|
13643
14393
|
}
|
|
14394
|
+
function typedBehindTheClear(messages, answers) {
|
|
14395
|
+
if (typeof answers !== "number") return [];
|
|
14396
|
+
let first = messages.length;
|
|
14397
|
+
while (first > 0) {
|
|
14398
|
+
const message = messages[first - 1];
|
|
14399
|
+
const typedAt = /^evt-(\d+)$/.exec(message.id);
|
|
14400
|
+
if (message.role !== "user" || !typedAt || Number(typedAt[1]) <= answers) break;
|
|
14401
|
+
first -= 1;
|
|
14402
|
+
}
|
|
14403
|
+
return messages.slice(first);
|
|
14404
|
+
}
|
|
13644
14405
|
function applyUserMessage(messages, eventId, data) {
|
|
13645
14406
|
const content = stringValue2(data.content);
|
|
13646
14407
|
const images = parseUserMessageImages(data.images);
|
|
@@ -13706,6 +14467,22 @@ function parseUserMessageAttachments(raw) {
|
|
|
13706
14467
|
}
|
|
13707
14468
|
return out.length > 0 ? out : void 0;
|
|
13708
14469
|
}
|
|
14470
|
+
function onComputer(event) {
|
|
14471
|
+
return event.data.computer === true ? { computer: true } : {};
|
|
14472
|
+
}
|
|
14473
|
+
function computerTab(event) {
|
|
14474
|
+
return event.data.computer === true ? stringValue2(event.data.session_id) : null;
|
|
14475
|
+
}
|
|
14476
|
+
var NO_BROWSER = "No supported browser on the chat's computer.";
|
|
14477
|
+
function saidLastOfBrowser(messages) {
|
|
14478
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
14479
|
+
const message = messages[index];
|
|
14480
|
+
if (message.systemKind === "browser_marker" || message.systemKind === "browser_marker_warning") {
|
|
14481
|
+
return message.content;
|
|
14482
|
+
}
|
|
14483
|
+
}
|
|
14484
|
+
return void 0;
|
|
14485
|
+
}
|
|
13709
14486
|
function applyBrowserEvent(state, event, browser) {
|
|
13710
14487
|
return withMessages(
|
|
13711
14488
|
{ ...state, browser },
|
|
@@ -13729,6 +14506,10 @@ function browserMarker(event) {
|
|
|
13729
14506
|
"browser.destroyed": {
|
|
13730
14507
|
content: "Browser closed.",
|
|
13731
14508
|
warning: false
|
|
14509
|
+
},
|
|
14510
|
+
"browser.unavailable": {
|
|
14511
|
+
content: NO_BROWSER,
|
|
14512
|
+
warning: true
|
|
13732
14513
|
}
|
|
13733
14514
|
};
|
|
13734
14515
|
const label = labels[event.type] ?? {
|
|
@@ -13976,6 +14757,67 @@ function applyTurnSummary(state, event) {
|
|
|
13976
14757
|
};
|
|
13977
14758
|
return { ...state, messages };
|
|
13978
14759
|
}
|
|
14760
|
+
function workerMessage(worker) {
|
|
14761
|
+
return {
|
|
14762
|
+
id: `worker-${worker.id}`,
|
|
14763
|
+
role: "system",
|
|
14764
|
+
content: worker.title ?? worker.goal,
|
|
14765
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
14766
|
+
status: "complete",
|
|
14767
|
+
systemKind: "worker",
|
|
14768
|
+
worker
|
|
14769
|
+
};
|
|
14770
|
+
}
|
|
14771
|
+
function applyWorkerSpawned(messages, event) {
|
|
14772
|
+
const id = stringValue2(event.data.worker_id);
|
|
14773
|
+
const proposalId = stringValue2(event.data.proposal_id);
|
|
14774
|
+
const key = stringValue2(event.data.key);
|
|
14775
|
+
const marked = proposalId && key ? messages.map((message) => message.proposal?.proposalId === proposalId ? { ...message, proposal: { ...message.proposal, started: { ...message.proposal.started, [key]: id } } } : message) : messages;
|
|
14776
|
+
if (marked.some((message) => message.worker?.id === id)) return marked;
|
|
14777
|
+
return [...marked, workerMessage({
|
|
14778
|
+
id,
|
|
14779
|
+
title: stringValue2(event.data.title) || null,
|
|
14780
|
+
goal: stringValue2(event.data.goal),
|
|
14781
|
+
state: "working",
|
|
14782
|
+
report: null,
|
|
14783
|
+
files: []
|
|
14784
|
+
})];
|
|
14785
|
+
}
|
|
14786
|
+
function applyWorkerReport(messages, event) {
|
|
14787
|
+
const id = stringValue2(event.data.worker_id);
|
|
14788
|
+
const failed = event.type === "worker.failed";
|
|
14789
|
+
const index = messages.findIndex((message) => message.worker?.id === id);
|
|
14790
|
+
if (index === -1) return messages;
|
|
14791
|
+
const worker = messages[index].worker;
|
|
14792
|
+
const reported = {
|
|
14793
|
+
...worker,
|
|
14794
|
+
state: failed ? "failed" : "reported",
|
|
14795
|
+
// A mission worker's result can carry the loop's control markup, which never reaches the UI.
|
|
14796
|
+
report: stripMissionControlMarkup(stringValue2(failed ? event.data.error : event.data.result)) || null,
|
|
14797
|
+
// A card lists files, as a thread's row does: not the URLs and commands a turn names.
|
|
14798
|
+
files: failed ? worker.files : parseTurnArtifacts(event.data.files).filter((file) => file.kind === "file" || file.kind === "artifact")
|
|
14799
|
+
};
|
|
14800
|
+
const next = [...messages];
|
|
14801
|
+
next[index] = { ...next[index], worker: reported };
|
|
14802
|
+
return next;
|
|
14803
|
+
}
|
|
14804
|
+
function proposalMessage(event) {
|
|
14805
|
+
const threads = (Array.isArray(event.data.threads) ? event.data.threads : []).filter((thread) => !!thread && typeof thread === "object").map((thread) => ({
|
|
14806
|
+
key: stringValue2(thread.key),
|
|
14807
|
+
title: stringValue2(thread.title),
|
|
14808
|
+
goal: stringValue2(thread.goal),
|
|
14809
|
+
where: thread.where === "device" ? "device" : "cloud"
|
|
14810
|
+
}));
|
|
14811
|
+
return {
|
|
14812
|
+
id: `proposal-${stringValue2(event.data.proposal_id)}`,
|
|
14813
|
+
role: "system",
|
|
14814
|
+
content: threads.map((thread) => thread.title).join(", "),
|
|
14815
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
14816
|
+
status: "complete",
|
|
14817
|
+
systemKind: "thread_proposal",
|
|
14818
|
+
proposal: { proposalId: stringValue2(event.data.proposal_id), threads, started: {} }
|
|
14819
|
+
};
|
|
14820
|
+
}
|
|
13979
14821
|
function parseTurnArtifacts(raw) {
|
|
13980
14822
|
if (!Array.isArray(raw)) return [];
|
|
13981
14823
|
const out = [];
|
|
@@ -14078,7 +14920,7 @@ function applyTerminalEvent(state, event) {
|
|
|
14078
14920
|
status: event.type === "session.fail" ? "error" : "complete",
|
|
14079
14921
|
errorInfo: errorInfo ?? doneMsg.errorInfo
|
|
14080
14922
|
};
|
|
14081
|
-
} else if (
|
|
14923
|
+
} else if (errorInfo) {
|
|
14082
14924
|
messages.push({
|
|
14083
14925
|
id: `error-${event.eventId}`,
|
|
14084
14926
|
role: "system",
|
|
@@ -14097,7 +14939,7 @@ function applyTerminalEvent(state, event) {
|
|
|
14097
14939
|
};
|
|
14098
14940
|
}
|
|
14099
14941
|
function buildErrorInfo(event) {
|
|
14100
|
-
if (event.type !== "session.fail" || typeof event.data.error_category !== "string") {
|
|
14942
|
+
if (event.type !== "session.fail" && event.type !== "session.pause" || typeof event.data.error_category !== "string") {
|
|
14101
14943
|
return void 0;
|
|
14102
14944
|
}
|
|
14103
14945
|
const detail = stringValue2(event.data.error_detail) || stringValue2(event.data.error);
|
|
@@ -14845,7 +15687,8 @@ function useAgentChatRuntime({
|
|
|
14845
15687
|
...prev,
|
|
14846
15688
|
browser: browserState ? {
|
|
14847
15689
|
status: browserState.status,
|
|
14848
|
-
controlOwner: browserState.controlOwner ?? null
|
|
15690
|
+
controlOwner: browserState.controlOwner ?? null,
|
|
15691
|
+
...browserState.computer ? { computer: true } : {}
|
|
14849
15692
|
} : null
|
|
14850
15693
|
}));
|
|
14851
15694
|
}).catch(() => void 0);
|
|
@@ -17346,17 +18189,21 @@ function saveDoc(adapter, sessionId, doc) {
|
|
|
17346
18189
|
file,
|
|
17347
18190
|
directory: CANVAS_DIR
|
|
17348
18191
|
});
|
|
18192
|
+
return true;
|
|
17349
18193
|
} catch {
|
|
18194
|
+
return false;
|
|
17350
18195
|
}
|
|
17351
18196
|
})();
|
|
17352
18197
|
inFlightSaves.set(sessionId, done);
|
|
17353
|
-
return done.then(() => {
|
|
18198
|
+
return done.then((saved) => {
|
|
17354
18199
|
if (inFlightSaves.get(sessionId) === done) {
|
|
17355
18200
|
inFlightSaves.delete(sessionId);
|
|
17356
18201
|
}
|
|
18202
|
+
return saved;
|
|
17357
18203
|
});
|
|
17358
18204
|
}
|
|
17359
18205
|
function useDebouncedSave(adapter, sessionId, doc, delayMs = SAVE_DEBOUNCE_MS) {
|
|
18206
|
+
const [unsaved, setUnsaved] = react.useState(false);
|
|
17360
18207
|
const latest = react.useRef(doc);
|
|
17361
18208
|
latest.current = doc;
|
|
17362
18209
|
const dirty = react.useRef(false);
|
|
@@ -17368,7 +18215,7 @@ function useDebouncedSave(adapter, sessionId, doc, delayMs = SAVE_DEBOUNCE_MS) {
|
|
|
17368
18215
|
}
|
|
17369
18216
|
dirty.current = true;
|
|
17370
18217
|
const timer = setTimeout(() => {
|
|
17371
|
-
void saveDoc(adapter, sessionId, latest.current);
|
|
18218
|
+
void saveDoc(adapter, sessionId, latest.current).then((saved) => setUnsaved(!saved));
|
|
17372
18219
|
}, delayMs);
|
|
17373
18220
|
return () => clearTimeout(timer);
|
|
17374
18221
|
}, [adapter, sessionId, doc, delayMs]);
|
|
@@ -17378,6 +18225,7 @@ function useDebouncedSave(adapter, sessionId, doc, delayMs = SAVE_DEBOUNCE_MS) {
|
|
|
17378
18225
|
void saveDoc(adapter, sessionId, latest.current);
|
|
17379
18226
|
};
|
|
17380
18227
|
}, [adapter, sessionId]);
|
|
18228
|
+
return unsaved;
|
|
17381
18229
|
}
|
|
17382
18230
|
function shouldReloadCanvas(previous, next, opts = {}) {
|
|
17383
18231
|
if (!next) return false;
|
|
@@ -17689,7 +18537,7 @@ function WhiteboardSurface({
|
|
|
17689
18537
|
return next;
|
|
17690
18538
|
});
|
|
17691
18539
|
}, [runtime.messages, canvasReady, resolveDraw]);
|
|
17692
|
-
useDebouncedSave(adapter, canvasReady ? sessionId : null, doc);
|
|
18540
|
+
const unsaved = useDebouncedSave(adapter, canvasReady ? sessionId : null, doc);
|
|
17693
18541
|
const hasAnswer = react.useMemo(
|
|
17694
18542
|
() => runtime.messages.some((m) => m.role === "assistant"),
|
|
17695
18543
|
[runtime.messages]
|
|
@@ -18302,6 +19150,7 @@ function WhiteboardSurface({
|
|
|
18302
19150
|
/* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: "size-4 shrink-0" }),
|
|
18303
19151
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: progress ?? (askMode === "deep" ? "Working it out\u2026" : "Thinking\u2026") })
|
|
18304
19152
|
] }) }) }) : null,
|
|
19153
|
+
unsaved ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute right-2 top-2 z-10", children: /* @__PURE__ */ jsxRuntime.jsx("span", { role: "status", className: "rounded-full border bg-background px-3 py-1 text-xs text-muted-foreground shadow-sm", children: "Your last change wasn't saved." }) }) : null,
|
|
18305
19154
|
restoringVisible ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/10", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-3 rounded-full border bg-background px-6 py-3 text-sm font-medium text-muted-foreground shadow-md", children: [
|
|
18306
19155
|
/* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: "size-5" }),
|
|
18307
19156
|
"Restoring board\u2026"
|
|
@@ -19045,7 +19894,38 @@ function FileTreeFile({
|
|
|
19045
19894
|
}
|
|
19046
19895
|
);
|
|
19047
19896
|
}
|
|
19897
|
+
var NONE = {};
|
|
19898
|
+
var chats = /* @__PURE__ */ new Map();
|
|
19899
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
19900
|
+
function changed() {
|
|
19901
|
+
for (const listener of listeners) listener();
|
|
19902
|
+
}
|
|
19903
|
+
function changeChatFiles(chat, changes) {
|
|
19904
|
+
chats.set(chat, { ...chats.get(chat), ...changes });
|
|
19905
|
+
changed();
|
|
19906
|
+
}
|
|
19907
|
+
function leaveChatFiles(chat) {
|
|
19908
|
+
const was = chats.get(chat);
|
|
19909
|
+
if (!was) return;
|
|
19910
|
+
const { entries: _tree, truncated: _cut, ...kept } = was;
|
|
19911
|
+
chats.set(chat, was.deleting ? kept : { ...kept, deleteTarget: null });
|
|
19912
|
+
changed();
|
|
19913
|
+
}
|
|
19914
|
+
function forgetChatFiles() {
|
|
19915
|
+
chats.clear();
|
|
19916
|
+
changed();
|
|
19917
|
+
}
|
|
19918
|
+
function subscribe2(listener) {
|
|
19919
|
+
listeners.add(listener);
|
|
19920
|
+
return () => {
|
|
19921
|
+
listeners.delete(listener);
|
|
19922
|
+
};
|
|
19923
|
+
}
|
|
19924
|
+
function useChatFiles(chat) {
|
|
19925
|
+
return react.useSyncExternalStore(subscribe2, () => chat === null ? NONE : chats.get(chat) ?? NONE);
|
|
19926
|
+
}
|
|
19048
19927
|
var SKELETON_WIDTHS2 = [75, 60, 90, 65, 80, 70, 85, 55];
|
|
19928
|
+
var NO_ENTRIES = [];
|
|
19049
19929
|
function collectExpandedPaths(entries, depth = 0) {
|
|
19050
19930
|
const paths = [];
|
|
19051
19931
|
for (const entry of entries) {
|
|
@@ -19159,43 +20039,69 @@ function WorkspacePanel({
|
|
|
19159
20039
|
disabled = false
|
|
19160
20040
|
}) {
|
|
19161
20041
|
const fileInputRef = react.useRef(null);
|
|
19162
|
-
const
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
|
|
19167
|
-
|
|
20042
|
+
const {
|
|
20043
|
+
entries = NO_ENTRIES,
|
|
20044
|
+
truncated = false,
|
|
20045
|
+
loading = false,
|
|
20046
|
+
error = null,
|
|
20047
|
+
waiting = null,
|
|
20048
|
+
notice = null,
|
|
20049
|
+
uploading = false,
|
|
20050
|
+
deleteTarget = null,
|
|
20051
|
+
deleting = false
|
|
20052
|
+
} = useChatFiles(sessionId);
|
|
19168
20053
|
const [expandedPaths, setExpandedPaths] = react.useState(/* @__PURE__ */ new Set());
|
|
19169
20054
|
const sessionIdRef = react.useRef(sessionId);
|
|
20055
|
+
const mountedRef = react.useRef(true);
|
|
20056
|
+
const readingRef = react.useRef(null);
|
|
19170
20057
|
sessionIdRef.current = sessionId;
|
|
20058
|
+
const stopReading = react.useCallback(() => {
|
|
20059
|
+
const was = readingRef.current;
|
|
20060
|
+
readingRef.current = null;
|
|
20061
|
+
if (!was) return;
|
|
20062
|
+
was.controller.abort();
|
|
20063
|
+
changeChatFiles(was.chat, { loading: false, waiting: null });
|
|
20064
|
+
}, []);
|
|
20065
|
+
react.useEffect(() => {
|
|
20066
|
+
mountedRef.current = true;
|
|
20067
|
+
return () => {
|
|
20068
|
+
mountedRef.current = false;
|
|
20069
|
+
stopReading();
|
|
20070
|
+
};
|
|
20071
|
+
}, [stopReading]);
|
|
20072
|
+
react.useEffect(() => () => {
|
|
20073
|
+
if (sessionId) leaveChatFiles(sessionId);
|
|
20074
|
+
}, [sessionId]);
|
|
19171
20075
|
const fetchTree = react.useCallback(async () => {
|
|
19172
|
-
if (!sessionId)
|
|
19173
|
-
|
|
19174
|
-
|
|
19175
|
-
|
|
19176
|
-
|
|
19177
|
-
|
|
19178
|
-
|
|
19179
|
-
|
|
19180
|
-
setTreeLoading(true);
|
|
19181
|
-
setTreeError(null);
|
|
20076
|
+
if (!mountedRef.current || sessionIdRef.current !== sessionId) return;
|
|
20077
|
+
stopReading();
|
|
20078
|
+
if (!sessionId) return;
|
|
20079
|
+
const chat = sessionId;
|
|
20080
|
+
const reading = { chat, controller: new AbortController() };
|
|
20081
|
+
readingRef.current = reading;
|
|
20082
|
+
const current = () => readingRef.current === reading;
|
|
20083
|
+
changeChatFiles(chat, { loading: true, waiting: null, error: null });
|
|
19182
20084
|
try {
|
|
19183
20085
|
const tree = await adapter.getWorkspaceTree({
|
|
19184
|
-
sessionId:
|
|
20086
|
+
sessionId: chat,
|
|
20087
|
+
signal: reading.controller.signal,
|
|
20088
|
+
onWaiting: (said) => {
|
|
20089
|
+
if (current()) changeChatFiles(chat, { waiting: said });
|
|
20090
|
+
}
|
|
19185
20091
|
});
|
|
19186
|
-
if (
|
|
19187
|
-
|
|
20092
|
+
if (!current()) return;
|
|
20093
|
+
changeChatFiles(chat, { entries: tree.entries, truncated: tree.truncated });
|
|
19188
20094
|
setExpandedPaths(new Set(collectExpandedPaths(tree.entries)));
|
|
19189
|
-
} catch (
|
|
19190
|
-
if (
|
|
19191
|
-
|
|
19192
|
-
setTreeError(error.message);
|
|
20095
|
+
} catch (error2) {
|
|
20096
|
+
if (!current()) return;
|
|
20097
|
+
changeChatFiles(chat, { entries: [], error: error2.message });
|
|
19193
20098
|
} finally {
|
|
19194
|
-
if (
|
|
19195
|
-
|
|
20099
|
+
if (current()) {
|
|
20100
|
+
readingRef.current = null;
|
|
20101
|
+
changeChatFiles(chat, { loading: false, waiting: null });
|
|
19196
20102
|
}
|
|
19197
20103
|
}
|
|
19198
|
-
}, [adapter, sessionId]);
|
|
20104
|
+
}, [adapter, sessionId, stopReading]);
|
|
19199
20105
|
react.useEffect(() => {
|
|
19200
20106
|
void fetchTree();
|
|
19201
20107
|
}, [fetchTree]);
|
|
@@ -19238,23 +20144,24 @@ function WorkspacePanel({
|
|
|
19238
20144
|
const handleUpload = react.useCallback(
|
|
19239
20145
|
async (files) => {
|
|
19240
20146
|
if (disabled || !sessionId || files.length === 0) return;
|
|
19241
|
-
|
|
19242
|
-
|
|
20147
|
+
const chat = sessionId;
|
|
20148
|
+
changeChatFiles(chat, { uploading: true, notice: null });
|
|
19243
20149
|
try {
|
|
19244
20150
|
for (const uploadedFile of Array.from(files)) {
|
|
19245
20151
|
await adapter.uploadWorkspaceFile({
|
|
19246
|
-
sessionId,
|
|
19247
|
-
file: uploadedFile
|
|
20152
|
+
sessionId: chat,
|
|
20153
|
+
file: uploadedFile,
|
|
20154
|
+
onWaiting: (said) => changeChatFiles(chat, { notice: said })
|
|
19248
20155
|
});
|
|
19249
20156
|
}
|
|
19250
|
-
|
|
19251
|
-
files.length === 1 ? `Uploaded ${files[0]?.name ?? "file"}` : `Uploaded ${files.length} files`
|
|
19252
|
-
);
|
|
20157
|
+
changeChatFiles(chat, {
|
|
20158
|
+
notice: files.length === 1 ? `Uploaded ${files[0]?.name ?? "file"}` : `Uploaded ${files.length} files`
|
|
20159
|
+
});
|
|
19253
20160
|
await fetchTree();
|
|
19254
|
-
} catch (
|
|
19255
|
-
|
|
20161
|
+
} catch (error2) {
|
|
20162
|
+
changeChatFiles(chat, { notice: error2.message });
|
|
19256
20163
|
} finally {
|
|
19257
|
-
|
|
20164
|
+
changeChatFiles(chat, { uploading: false });
|
|
19258
20165
|
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
19259
20166
|
}
|
|
19260
20167
|
},
|
|
@@ -19263,17 +20170,23 @@ function WorkspacePanel({
|
|
|
19263
20170
|
const handleDelete = react.useCallback(
|
|
19264
20171
|
async (path) => {
|
|
19265
20172
|
if (disabled || !sessionId) return;
|
|
20173
|
+
const chat = sessionId;
|
|
20174
|
+
changeChatFiles(chat, { deleting: true });
|
|
19266
20175
|
try {
|
|
19267
|
-
await adapter.deleteWorkspaceFile({
|
|
19268
|
-
|
|
20176
|
+
await adapter.deleteWorkspaceFile({
|
|
20177
|
+
sessionId: chat,
|
|
20178
|
+
path,
|
|
20179
|
+
onWaiting: (said) => changeChatFiles(chat, { notice: said })
|
|
20180
|
+
});
|
|
20181
|
+
if (mountedRef.current && sessionIdRef.current === chat && selectedPath === path) {
|
|
19269
20182
|
onSelectedPathChange(null);
|
|
19270
20183
|
}
|
|
19271
|
-
|
|
20184
|
+
changeChatFiles(chat, { notice: `Deleted ${path.split("/").pop() ?? path}` });
|
|
19272
20185
|
await fetchTree();
|
|
19273
|
-
} catch (
|
|
19274
|
-
|
|
20186
|
+
} catch (error2) {
|
|
20187
|
+
changeChatFiles(chat, { notice: error2.message });
|
|
19275
20188
|
} finally {
|
|
19276
|
-
|
|
20189
|
+
changeChatFiles(chat, { deleteTarget: null, deleting: false });
|
|
19277
20190
|
}
|
|
19278
20191
|
},
|
|
19279
20192
|
[
|
|
@@ -19331,12 +20244,12 @@ function WorkspacePanel({
|
|
|
19331
20244
|
variant: "ghost",
|
|
19332
20245
|
size: "icon-sm",
|
|
19333
20246
|
onClick: () => void fetchTree(),
|
|
19334
|
-
disabled: !sessionId ||
|
|
20247
|
+
disabled: !sessionId || loading,
|
|
19335
20248
|
"aria-label": "Refresh workspace",
|
|
19336
20249
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
19337
20250
|
lucideReact.RefreshCwIcon,
|
|
19338
20251
|
{
|
|
19339
|
-
className: cn("size-4",
|
|
20252
|
+
className: cn("size-4", loading && "animate-spin")
|
|
19340
20253
|
}
|
|
19341
20254
|
)
|
|
19342
20255
|
}
|
|
@@ -19344,9 +20257,26 @@ function WorkspacePanel({
|
|
|
19344
20257
|
/* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: "Refresh" })
|
|
19345
20258
|
] })
|
|
19346
20259
|
] }),
|
|
19347
|
-
notice && /* @__PURE__ */ jsxRuntime.jsx(
|
|
20260
|
+
notice && /* @__PURE__ */ jsxRuntime.jsx(
|
|
20261
|
+
"div",
|
|
20262
|
+
{
|
|
20263
|
+
"data-testid": "workspace-notice",
|
|
20264
|
+
className: "border-b border-line px-3 py-2 text-xs text-muted-foreground",
|
|
20265
|
+
children: notice
|
|
20266
|
+
}
|
|
20267
|
+
),
|
|
20268
|
+
truncated && entries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-line px-3 py-2 text-xs text-muted-foreground", children: "Some files are not shown." }),
|
|
19348
20269
|
/* @__PURE__ */ jsxRuntime.jsx(ScrollArea, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-1 py-1", children: [
|
|
19349
|
-
|
|
20270
|
+
waiting && /* @__PURE__ */ jsxRuntime.jsx(
|
|
20271
|
+
"div",
|
|
20272
|
+
{
|
|
20273
|
+
role: "status",
|
|
20274
|
+
"data-testid": "tree-waiting",
|
|
20275
|
+
className: "px-3 py-2 text-sm text-muted-foreground",
|
|
20276
|
+
children: waiting
|
|
20277
|
+
}
|
|
20278
|
+
),
|
|
20279
|
+
loading && !waiting && entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 p-2", children: Array.from({ length: 8 }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
19350
20280
|
Skeleton,
|
|
19351
20281
|
{
|
|
19352
20282
|
className: "h-5 rounded",
|
|
@@ -19356,11 +20286,11 @@ function WorkspacePanel({
|
|
|
19356
20286
|
},
|
|
19357
20287
|
index
|
|
19358
20288
|
)) }),
|
|
19359
|
-
|
|
20289
|
+
error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-sm text-destructive", children: [
|
|
19360
20290
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-4 shrink-0" }),
|
|
19361
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
20291
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
|
|
19362
20292
|
] }),
|
|
19363
|
-
!
|
|
20293
|
+
!loading && !error && entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-8 text-center text-sm text-faint", children: [
|
|
19364
20294
|
/* @__PURE__ */ jsxRuntime.jsx("p", { children: "No workspace files" }),
|
|
19365
20295
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
19366
20296
|
Button,
|
|
@@ -19389,7 +20319,9 @@ function WorkspacePanel({
|
|
|
19389
20319
|
RenderEntries,
|
|
19390
20320
|
{
|
|
19391
20321
|
entries,
|
|
19392
|
-
onDelete:
|
|
20322
|
+
onDelete: (path) => {
|
|
20323
|
+
if (sessionId) changeChatFiles(sessionId, { deleteTarget: path });
|
|
20324
|
+
},
|
|
19393
20325
|
downloadUrlFor: (path) => sessionId ? adapter.getWorkspaceDownloadUrl({ sessionId, path }) : "#"
|
|
19394
20326
|
}
|
|
19395
20327
|
)
|
|
@@ -19403,8 +20335,13 @@ function WorkspacePanel({
|
|
|
19403
20335
|
title: "Delete file?",
|
|
19404
20336
|
description: `This will permanently delete ${deleteTarget?.split("/").pop() ?? "this file"} from the workspace.`,
|
|
19405
20337
|
confirmLabel: "Delete",
|
|
19406
|
-
|
|
19407
|
-
|
|
20338
|
+
loading: deleting,
|
|
20339
|
+
onConfirm: () => {
|
|
20340
|
+
if (deleteTarget) void handleDelete(deleteTarget);
|
|
20341
|
+
},
|
|
20342
|
+
onCancel: () => {
|
|
20343
|
+
if (sessionId) changeChatFiles(sessionId, { deleteTarget: null });
|
|
20344
|
+
}
|
|
19408
20345
|
}
|
|
19409
20346
|
)
|
|
19410
20347
|
]
|
|
@@ -19417,18 +20354,10 @@ function ConfirmDialog2({
|
|
|
19417
20354
|
title,
|
|
19418
20355
|
description,
|
|
19419
20356
|
confirmLabel,
|
|
20357
|
+
loading,
|
|
19420
20358
|
onConfirm,
|
|
19421
20359
|
onCancel
|
|
19422
20360
|
}) {
|
|
19423
|
-
const [loading, setLoading] = react.useState(false);
|
|
19424
|
-
const handleConfirm = async () => {
|
|
19425
|
-
setLoading(true);
|
|
19426
|
-
try {
|
|
19427
|
-
await onConfirm();
|
|
19428
|
-
} finally {
|
|
19429
|
-
setLoading(false);
|
|
19430
|
-
}
|
|
19431
|
-
};
|
|
19432
20361
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
19433
20362
|
Dialog,
|
|
19434
20363
|
{
|
|
@@ -19448,7 +20377,7 @@ function ConfirmDialog2({
|
|
|
19448
20377
|
{
|
|
19449
20378
|
variant: "destructive",
|
|
19450
20379
|
disabled: loading,
|
|
19451
|
-
onClick:
|
|
20380
|
+
onClick: onConfirm,
|
|
19452
20381
|
children: [
|
|
19453
20382
|
loading && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "mr-1.5 size-3.5 animate-spin" }),
|
|
19454
20383
|
confirmLabel
|
|
@@ -19465,6 +20394,10 @@ function ConfirmDialog2({
|
|
|
19465
20394
|
init_utils();
|
|
19466
20395
|
|
|
19467
20396
|
// src/lib/sessions.ts
|
|
20397
|
+
function sessionComputer(config) {
|
|
20398
|
+
const execution = config?.execution;
|
|
20399
|
+
return execution?.kind === "device" && typeof execution.device_name === "string" ? execution.device_name : null;
|
|
20400
|
+
}
|
|
19468
20401
|
function isScheduledRunSession(session) {
|
|
19469
20402
|
return Boolean(
|
|
19470
20403
|
session?.channel === "scheduled" || session?.config?.scheduled_session_id
|
|
@@ -19525,7 +20458,11 @@ function AgentChat({
|
|
|
19525
20458
|
goalsEnabled = true,
|
|
19526
20459
|
compressEnabled = true,
|
|
19527
20460
|
onOpenIntegrations,
|
|
19528
|
-
onOpenBilling
|
|
20461
|
+
onOpenBilling,
|
|
20462
|
+
composerFooter,
|
|
20463
|
+
firstMessage,
|
|
20464
|
+
onFirstMessageSent,
|
|
20465
|
+
computerBrowser
|
|
19529
20466
|
}) {
|
|
19530
20467
|
const [workspacePath, setWorkspacePath] = react.useState(null);
|
|
19531
20468
|
const [previewPath, setPreviewPath] = react.useState(null);
|
|
@@ -19538,6 +20475,9 @@ function AgentChat({
|
|
|
19538
20475
|
sessionId,
|
|
19539
20476
|
onSessionChange
|
|
19540
20477
|
});
|
|
20478
|
+
const sessionConfig = runtime.session?.config;
|
|
20479
|
+
const projectId = sessionConfig?.workstream_role === "coordinator" && typeof sessionConfig.workstream_id === "string" ? sessionConfig.workstream_id : null;
|
|
20480
|
+
const threadRows = useProjectThreads(adapter, projectId);
|
|
19541
20481
|
react.useEffect(() => {
|
|
19542
20482
|
setOpenPane(null);
|
|
19543
20483
|
setFilesOpen(false);
|
|
@@ -19565,13 +20505,14 @@ function AgentChat({
|
|
|
19565
20505
|
const browserState = runtime.state.browser;
|
|
19566
20506
|
const browserAvailable = browserState !== null && browserState.status !== "closed" && !!sessionId;
|
|
19567
20507
|
const browserVisible = browserAvailable && openPane === "browser";
|
|
20508
|
+
const browserOnComputer = browserState?.computer === true;
|
|
19568
20509
|
const workspaceAvailable = !!sessionId;
|
|
19569
20510
|
const filePreviewVisible = workspaceAvailable && previewPath !== null && openPane === "file";
|
|
19570
20511
|
const rightStackVisible = browserVisible || filePreviewVisible;
|
|
19571
20512
|
const browserPreview = useBrowserPreview({
|
|
19572
20513
|
adapter,
|
|
19573
20514
|
sessionId,
|
|
19574
|
-
enabled: browserAvailable
|
|
20515
|
+
enabled: browserAvailable && !browserOnComputer
|
|
19575
20516
|
});
|
|
19576
20517
|
const browserRunning = browserAvailable;
|
|
19577
20518
|
react.useEffect(() => {
|
|
@@ -19582,6 +20523,46 @@ function AgentChat({
|
|
|
19582
20523
|
react.useEffect(() => {
|
|
19583
20524
|
onMessagesChange?.(runtime.messages);
|
|
19584
20525
|
}, [onMessagesChange, runtime.messages]);
|
|
20526
|
+
const firstSent = react.useRef(null);
|
|
20527
|
+
const first = react.useRef({ message: firstMessage ?? null, told: onFirstMessageSent });
|
|
20528
|
+
react.useEffect(() => {
|
|
20529
|
+
first.current = { message: firstMessage ?? null, told: onFirstMessageSent };
|
|
20530
|
+
});
|
|
20531
|
+
const send = runtime.send;
|
|
20532
|
+
react.useEffect(() => {
|
|
20533
|
+
if (!firstMessage || firstSent.current === firstMessage.id) return;
|
|
20534
|
+
if (sessionId !== null) {
|
|
20535
|
+
firstSent.current = firstMessage.id;
|
|
20536
|
+
first.current.told?.(firstMessage.id, "Another chat opened before this one was made, so nothing was sent.");
|
|
20537
|
+
return;
|
|
20538
|
+
}
|
|
20539
|
+
if (effectiveDisabled) return;
|
|
20540
|
+
let live = true;
|
|
20541
|
+
queueMicrotask(() => {
|
|
20542
|
+
if (!live || firstSent.current === firstMessage.id) return;
|
|
20543
|
+
firstSent.current = firstMessage.id;
|
|
20544
|
+
send(firstMessage.text).then(
|
|
20545
|
+
() => first.current.told?.(firstMessage.id, null),
|
|
20546
|
+
(error) => first.current.told?.(firstMessage.id, error instanceof Error ? error.message : String(error))
|
|
20547
|
+
);
|
|
20548
|
+
});
|
|
20549
|
+
return () => {
|
|
20550
|
+
live = false;
|
|
20551
|
+
};
|
|
20552
|
+
}, [firstMessage, sessionId, effectiveDisabled, send]);
|
|
20553
|
+
const mounted = react.useRef(false);
|
|
20554
|
+
react.useEffect(() => {
|
|
20555
|
+
mounted.current = true;
|
|
20556
|
+
return () => {
|
|
20557
|
+
mounted.current = false;
|
|
20558
|
+
queueMicrotask(() => {
|
|
20559
|
+
const { message, told } = first.current;
|
|
20560
|
+
if (mounted.current || !message || firstSent.current === message.id) return;
|
|
20561
|
+
firstSent.current = message.id;
|
|
20562
|
+
told?.(message.id, "The page left the new chat before it was made, so nothing was sent.");
|
|
20563
|
+
});
|
|
20564
|
+
};
|
|
20565
|
+
}, []);
|
|
19585
20566
|
const handleOpenFilePreview = react.useCallback((path) => {
|
|
19586
20567
|
setWorkspacePath(path);
|
|
19587
20568
|
setPreviewPath(path);
|
|
@@ -19621,7 +20602,11 @@ function AgentChat({
|
|
|
19621
20602
|
adapter,
|
|
19622
20603
|
sessionId,
|
|
19623
20604
|
onFileSelect: handleFileSelect,
|
|
19624
|
-
onOpenBilling
|
|
20605
|
+
onOpenBilling,
|
|
20606
|
+
onOpenSession: onSessionChange,
|
|
20607
|
+
projectId,
|
|
20608
|
+
threadRows,
|
|
20609
|
+
readOnly: disabled === true
|
|
19625
20610
|
},
|
|
19626
20611
|
children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
19627
20612
|
"section",
|
|
@@ -19703,7 +20688,7 @@ function AgentChat({
|
|
|
19703
20688
|
isLoadingHistory: runtime.isLoadingHistory,
|
|
19704
20689
|
onSend: (content, images, attachments) => runtime.send(content, images, attachments),
|
|
19705
20690
|
onStop: () => runtime.stop(),
|
|
19706
|
-
onRetry: runtime.retry,
|
|
20691
|
+
onRetry: disabled ? void 0 : runtime.retry,
|
|
19707
20692
|
onFileSelect: handleFileSelect,
|
|
19708
20693
|
disabled: effectiveDisabled,
|
|
19709
20694
|
disabledReason,
|
|
@@ -19721,8 +20706,9 @@ function AgentChat({
|
|
|
19721
20706
|
canShowBrowser: browserAvailable,
|
|
19722
20707
|
canShowWorkspace: workspaceAvailable,
|
|
19723
20708
|
paneCards: {
|
|
19724
|
-
browser:
|
|
19725
|
-
|
|
20709
|
+
// A chat the host only reads offers its browser no card: the card is the way to take it over.
|
|
20710
|
+
browser: browserRunning && !disabled ? {
|
|
20711
|
+
subtitle: browserOnComputer ? browserState?.status === "unavailable" ? "No supported browser on its computer" : "On the chat's computer" : browserState?.controlOwner ? `${browserState.controlOwner} has control` : void 0,
|
|
19726
20712
|
thumbnail: browserPreview,
|
|
19727
20713
|
// Show and hide on the same card, like the Files
|
|
19728
20714
|
// accordion header and the composer's Tools item.
|
|
@@ -19758,7 +20744,9 @@ function AgentChat({
|
|
|
19758
20744
|
researchSources: runtime.researchSources,
|
|
19759
20745
|
hideTurnSummary,
|
|
19760
20746
|
agentId,
|
|
19761
|
-
onOpenIntegrations
|
|
20747
|
+
onOpenIntegrations,
|
|
20748
|
+
deviceWait: runtime.state.deviceWait,
|
|
20749
|
+
composerFooter
|
|
19762
20750
|
}
|
|
19763
20751
|
)
|
|
19764
20752
|
}
|
|
@@ -19784,13 +20772,21 @@ function AgentChat({
|
|
|
19784
20772
|
"data-testid": "browser-panel",
|
|
19785
20773
|
"data-mobile-view": activeMobilePane,
|
|
19786
20774
|
className: "min-h-0 h-full w-full overflow-hidden",
|
|
19787
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
20775
|
+
children: browserOnComputer ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
20776
|
+
ComputerBrowserPane,
|
|
20777
|
+
{
|
|
20778
|
+
available: browserState.status !== "unavailable",
|
|
20779
|
+
readOnly: disabled === true,
|
|
20780
|
+
draw: computerBrowser
|
|
20781
|
+
}
|
|
20782
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
19788
20783
|
BrowserPane,
|
|
19789
20784
|
{
|
|
19790
20785
|
sessionId,
|
|
19791
20786
|
state: browserState,
|
|
19792
20787
|
adapter,
|
|
19793
|
-
onClose: () => setOpenPane(null)
|
|
20788
|
+
onClose: () => setOpenPane(null),
|
|
20789
|
+
readOnly: disabled === true
|
|
19794
20790
|
}
|
|
19795
20791
|
)
|
|
19796
20792
|
}
|
|
@@ -19895,9 +20891,9 @@ var FetchSseEventStream = class {
|
|
|
19895
20891
|
queueMicrotask(() => void this.start());
|
|
19896
20892
|
}
|
|
19897
20893
|
addEventListener(type, listener) {
|
|
19898
|
-
const
|
|
19899
|
-
|
|
19900
|
-
this.listeners.set(type,
|
|
20894
|
+
const listeners2 = this.listeners.get(type) ?? /* @__PURE__ */ new Set();
|
|
20895
|
+
listeners2.add(listener);
|
|
20896
|
+
this.listeners.set(type, listeners2);
|
|
19901
20897
|
}
|
|
19902
20898
|
close() {
|
|
19903
20899
|
this.watchdog.stop();
|
|
@@ -19974,13 +20970,13 @@ var FetchSseEventStream = class {
|
|
|
19974
20970
|
this.dataLines = [];
|
|
19975
20971
|
return;
|
|
19976
20972
|
}
|
|
19977
|
-
const
|
|
20973
|
+
const listeners2 = this.listeners.get(this.eventType);
|
|
19978
20974
|
const event = {
|
|
19979
20975
|
data: this.dataLines.join("\n"),
|
|
19980
20976
|
lastEventId: this.eventId
|
|
19981
20977
|
};
|
|
19982
|
-
if (
|
|
19983
|
-
for (const listener of
|
|
20978
|
+
if (listeners2) {
|
|
20979
|
+
for (const listener of listeners2) {
|
|
19984
20980
|
listener(event);
|
|
19985
20981
|
}
|
|
19986
20982
|
}
|
|
@@ -21383,218 +22379,6 @@ function CardContent({ className, ...props }) {
|
|
|
21383
22379
|
}
|
|
21384
22380
|
);
|
|
21385
22381
|
}
|
|
21386
|
-
|
|
21387
|
-
// src/components/missions/mission-derive.ts
|
|
21388
|
-
var ACTIVE_MISSION_STATUSES = /* @__PURE__ */ new Set(["active", "paused"]);
|
|
21389
|
-
function isTerminalMissionStatus(status) {
|
|
21390
|
-
return !ACTIVE_MISSION_STATUSES.has(status);
|
|
21391
|
-
}
|
|
21392
|
-
function missionTaskBucket(status) {
|
|
21393
|
-
if (status === "done") return "done";
|
|
21394
|
-
if (status === "blocked") return "blocked";
|
|
21395
|
-
if (status === "failed" || status === "cancelled") {
|
|
21396
|
-
return "failed_or_cancelled";
|
|
21397
|
-
}
|
|
21398
|
-
return "in_flight";
|
|
21399
|
-
}
|
|
21400
|
-
function groupMissionTasksByBucket(tasks) {
|
|
21401
|
-
const buckets = {
|
|
21402
|
-
in_flight: [],
|
|
21403
|
-
done: [],
|
|
21404
|
-
blocked: [],
|
|
21405
|
-
failed_or_cancelled: []
|
|
21406
|
-
};
|
|
21407
|
-
for (const t of tasks) {
|
|
21408
|
-
buckets[missionTaskBucket(t.status)].push(t);
|
|
21409
|
-
}
|
|
21410
|
-
return buckets;
|
|
21411
|
-
}
|
|
21412
|
-
function deriveMissionWorkerActivityLabel(worker) {
|
|
21413
|
-
const kind = worker.latestEventKind;
|
|
21414
|
-
if (kind === "tool.call" && worker.latestEventSummary) {
|
|
21415
|
-
return `tool: ${truncate4(worker.latestEventSummary, 80)}`;
|
|
21416
|
-
}
|
|
21417
|
-
if (kind === "llm.response") {
|
|
21418
|
-
return "thinking";
|
|
21419
|
-
}
|
|
21420
|
-
if (kind === "tool.result") {
|
|
21421
|
-
return "received tool result";
|
|
21422
|
-
}
|
|
21423
|
-
if (kind && worker.latestEventSummary) {
|
|
21424
|
-
return `${kind}: ${truncate4(worker.latestEventSummary, 60)}`;
|
|
21425
|
-
}
|
|
21426
|
-
return worker.sessionStatus || "active";
|
|
21427
|
-
}
|
|
21428
|
-
function truncate4(s, max) {
|
|
21429
|
-
if (s.length <= max) return s;
|
|
21430
|
-
return `${s.slice(0, max - 1)}\u2026`;
|
|
21431
|
-
}
|
|
21432
|
-
var MISSION_EVENT_CATEGORIES = [
|
|
21433
|
-
"spawn",
|
|
21434
|
-
"output",
|
|
21435
|
-
"done",
|
|
21436
|
-
"verdict",
|
|
21437
|
-
"system"
|
|
21438
|
-
];
|
|
21439
|
-
function missionEventCategory(type) {
|
|
21440
|
-
switch (type) {
|
|
21441
|
-
case "worker.spawned":
|
|
21442
|
-
return "spawn";
|
|
21443
|
-
case "iteration.summary":
|
|
21444
|
-
return "output";
|
|
21445
|
-
case "worker.complete":
|
|
21446
|
-
return "done";
|
|
21447
|
-
case "mission.evaluation.end":
|
|
21448
|
-
return "verdict";
|
|
21449
|
-
default:
|
|
21450
|
-
return "system";
|
|
21451
|
-
}
|
|
21452
|
-
}
|
|
21453
|
-
function dataString(data, key) {
|
|
21454
|
-
const v = data?.[key];
|
|
21455
|
-
return typeof v === "string" && v.trim().length > 0 ? v.trim() : null;
|
|
21456
|
-
}
|
|
21457
|
-
var CONTROL_MARKUP_RE = /<next_action\b[^>]*(?:\/>|>[\s\S]*?<\/next_action>)/gi;
|
|
21458
|
-
function stripMissionControlMarkup(text) {
|
|
21459
|
-
return text.replace(CONTROL_MARKUP_RE, "").trim();
|
|
21460
|
-
}
|
|
21461
|
-
function missionEventSummary(event) {
|
|
21462
|
-
return stripMissionControlMarkup(rawMissionEventSummary(event));
|
|
21463
|
-
}
|
|
21464
|
-
function rawMissionEventSummary(event) {
|
|
21465
|
-
const d = event.data ?? null;
|
|
21466
|
-
switch (event.type) {
|
|
21467
|
-
case "worker.spawned":
|
|
21468
|
-
return dataString(d, "goal") ?? "Task spawned";
|
|
21469
|
-
case "worker.complete":
|
|
21470
|
-
return dataString(d, "result") ?? "Worker complete";
|
|
21471
|
-
case "iteration.summary":
|
|
21472
|
-
return dataString(d, "summary") ?? "";
|
|
21473
|
-
case "mission.evaluation.end": {
|
|
21474
|
-
const result = dataString(d, "result");
|
|
21475
|
-
const feedback = dataString(d, "feedback") ?? dataString(d, "explanation");
|
|
21476
|
-
if (result && feedback) return `${result} \u2014 ${feedback}`;
|
|
21477
|
-
return result ?? feedback ?? "Evaluation finished";
|
|
21478
|
-
}
|
|
21479
|
-
case "task.blocked":
|
|
21480
|
-
return dataString(d, "reason") ?? "Task blocked";
|
|
21481
|
-
case "task.failed":
|
|
21482
|
-
return "Task failed";
|
|
21483
|
-
default: {
|
|
21484
|
-
const text = dataString(d, "summary") ?? dataString(d, "text") ?? dataString(d, "message") ?? dataString(d, "reason");
|
|
21485
|
-
if (text) return text;
|
|
21486
|
-
if (!d) return "";
|
|
21487
|
-
try {
|
|
21488
|
-
return truncate4(JSON.stringify(d), 160);
|
|
21489
|
-
} catch {
|
|
21490
|
-
return "";
|
|
21491
|
-
}
|
|
21492
|
-
}
|
|
21493
|
-
}
|
|
21494
|
-
}
|
|
21495
|
-
function missionEventTaskId(event, sessions) {
|
|
21496
|
-
return dataString(event.data ?? null, "task_id") ?? sessions[event.sessionId]?.taskId ?? null;
|
|
21497
|
-
}
|
|
21498
|
-
function missionEventActorLabel(event, sessions) {
|
|
21499
|
-
const meta = sessions[event.sessionId];
|
|
21500
|
-
if (!meta) return event.sessionId.slice(0, 8);
|
|
21501
|
-
if (meta.kind === "coordinator") return "orchestrator";
|
|
21502
|
-
return meta.agentDefName ?? "worker";
|
|
21503
|
-
}
|
|
21504
|
-
function missionTaskBlocks(tasks) {
|
|
21505
|
-
const blocks = {};
|
|
21506
|
-
for (const t of tasks) {
|
|
21507
|
-
for (const parent of t.parentIds) {
|
|
21508
|
-
(blocks[parent] ?? (blocks[parent] = [])).push(t.id);
|
|
21509
|
-
}
|
|
21510
|
-
}
|
|
21511
|
-
return blocks;
|
|
21512
|
-
}
|
|
21513
|
-
function missionTaskTitle(goal, max = 80) {
|
|
21514
|
-
const line = goal.split("\n").find((l) => l.trim().length > 0)?.trim() ?? goal.trim();
|
|
21515
|
-
return truncate4(line.replace(/\s+/g, " "), max);
|
|
21516
|
-
}
|
|
21517
|
-
var RAIL_GROUP_DEFS = [
|
|
21518
|
-
{ key: "running", label: "Running", match: (s) => s === "running" },
|
|
21519
|
-
{ key: "blocked", label: "Blocked", match: (s) => s === "blocked" },
|
|
21520
|
-
{
|
|
21521
|
-
key: "queued",
|
|
21522
|
-
label: "Queued",
|
|
21523
|
-
match: (s) => s === "ready" || s === "todo"
|
|
21524
|
-
},
|
|
21525
|
-
{ key: "done", label: "Done", match: (s) => s === "done" },
|
|
21526
|
-
{
|
|
21527
|
-
key: "failed",
|
|
21528
|
-
label: "Failed / cancelled",
|
|
21529
|
-
match: (s) => s === "failed" || s === "cancelled"
|
|
21530
|
-
}
|
|
21531
|
-
];
|
|
21532
|
-
function missionTaskRailGroups(tasks) {
|
|
21533
|
-
const grouped = /* @__PURE__ */ new Set();
|
|
21534
|
-
const groups = RAIL_GROUP_DEFS.map((def) => ({
|
|
21535
|
-
key: def.key,
|
|
21536
|
-
label: def.label,
|
|
21537
|
-
tasks: tasks.filter((t) => {
|
|
21538
|
-
const hit = def.match(t.status);
|
|
21539
|
-
if (hit) grouped.add(t.id);
|
|
21540
|
-
return hit;
|
|
21541
|
-
})
|
|
21542
|
-
}));
|
|
21543
|
-
const stray = tasks.filter((t) => !grouped.has(t.id));
|
|
21544
|
-
if (stray.length > 0) {
|
|
21545
|
-
const queued = groups.find((g) => g.key === "queued");
|
|
21546
|
-
if (queued) queued.tasks.push(...stray);
|
|
21547
|
-
}
|
|
21548
|
-
return groups.filter((g) => g.tasks.length > 0);
|
|
21549
|
-
}
|
|
21550
|
-
var TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
21551
|
-
function defaultSelectedMissionTaskId(tasks) {
|
|
21552
|
-
if (tasks.length === 0) return null;
|
|
21553
|
-
const ordered = missionTaskRailGroups(tasks).flatMap((g) => g.tasks);
|
|
21554
|
-
const firstLive = ordered.find(
|
|
21555
|
-
(t) => !TERMINAL_TASK_STATUSES.has(t.status)
|
|
21556
|
-
);
|
|
21557
|
-
return (firstLive ?? ordered[0] ?? tasks[0]).id;
|
|
21558
|
-
}
|
|
21559
|
-
function mergeMissionEvents(existing, incoming) {
|
|
21560
|
-
if (incoming.length === 0) return existing;
|
|
21561
|
-
const byId = /* @__PURE__ */ new Map();
|
|
21562
|
-
for (const e of existing) byId.set(e.id, e);
|
|
21563
|
-
for (const e of incoming) byId.set(e.id, e);
|
|
21564
|
-
return [...byId.values()].sort((a, b) => a.id - b.id);
|
|
21565
|
-
}
|
|
21566
|
-
function missionTaskStatusDotClass(status) {
|
|
21567
|
-
switch (status) {
|
|
21568
|
-
case "running":
|
|
21569
|
-
return "bg-primary";
|
|
21570
|
-
case "blocked":
|
|
21571
|
-
return "bg-amber-500";
|
|
21572
|
-
case "done":
|
|
21573
|
-
return "bg-emerald-500";
|
|
21574
|
-
case "failed":
|
|
21575
|
-
return "bg-destructive";
|
|
21576
|
-
case "cancelled":
|
|
21577
|
-
return "bg-muted-foreground";
|
|
21578
|
-
default:
|
|
21579
|
-
return "bg-foreground/30";
|
|
21580
|
-
}
|
|
21581
|
-
}
|
|
21582
|
-
function missionWorkerTaskCounts(worker, tasks) {
|
|
21583
|
-
if (worker.kind !== "task") return null;
|
|
21584
|
-
const mine = tasks.filter((t) => t.agentDefName === worker.agentDefName);
|
|
21585
|
-
return {
|
|
21586
|
-
completed: mine.filter((t) => t.status === "done").length,
|
|
21587
|
-
inFlight: mine.filter(
|
|
21588
|
-
(t) => t.status === "running" || t.status === "blocked"
|
|
21589
|
-
).length
|
|
21590
|
-
};
|
|
21591
|
-
}
|
|
21592
|
-
function formatMissionTimestamp(iso) {
|
|
21593
|
-
if (!iso) return "";
|
|
21594
|
-
const parsed = new Date(iso);
|
|
21595
|
-
if (Number.isNaN(parsed.getTime())) return iso;
|
|
21596
|
-
return parsed.toLocaleTimeString();
|
|
21597
|
-
}
|
|
21598
22382
|
var CATEGORY_BADGE_CLASS = {
|
|
21599
22383
|
spawn: "border-primary/30 bg-primary/10 text-primary",
|
|
21600
22384
|
output: "border-foreground/20 bg-foreground/5 text-foreground/80",
|
|
@@ -22247,7 +23031,7 @@ function MissionWorkersTab({
|
|
|
22247
23031
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-8 text-center text-sm text-muted-foreground/60", children: "No workers attached to this mission right now." });
|
|
22248
23032
|
}
|
|
22249
23033
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: workers.map((w) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
22250
|
-
|
|
23034
|
+
WorkerCard2,
|
|
22251
23035
|
{
|
|
22252
23036
|
worker: w,
|
|
22253
23037
|
tasks,
|
|
@@ -22256,7 +23040,7 @@ function MissionWorkersTab({
|
|
|
22256
23040
|
w.workerSessionId
|
|
22257
23041
|
)) });
|
|
22258
23042
|
}
|
|
22259
|
-
function
|
|
23043
|
+
function WorkerCard2({
|
|
22260
23044
|
worker,
|
|
22261
23045
|
tasks,
|
|
22262
23046
|
onOpenTranscript
|
|
@@ -23444,6 +24228,7 @@ function sessionToTreeNode(session) {
|
|
|
23444
24228
|
model: session.model,
|
|
23445
24229
|
messageCount: session.messageCount,
|
|
23446
24230
|
toolCallCount: session.toolCallCount,
|
|
24231
|
+
computer: sessionComputer(session.config),
|
|
23447
24232
|
createdAt: session.createdAt ?? timestamp,
|
|
23448
24233
|
updatedAt: session.updatedAt ?? timestamp
|
|
23449
24234
|
};
|
|
@@ -23470,13 +24255,13 @@ function mergeTreeNodes(groups) {
|
|
|
23470
24255
|
}
|
|
23471
24256
|
function collectDeletedSessionIds(nodes, deletedSessionId) {
|
|
23472
24257
|
const deletedIds = /* @__PURE__ */ new Set([deletedSessionId]);
|
|
23473
|
-
let
|
|
23474
|
-
while (
|
|
23475
|
-
|
|
24258
|
+
let changed2 = true;
|
|
24259
|
+
while (changed2) {
|
|
24260
|
+
changed2 = false;
|
|
23476
24261
|
for (const node of nodes) {
|
|
23477
24262
|
if (node.parentId && deletedIds.has(node.parentId) && !deletedIds.has(node.id)) {
|
|
23478
24263
|
deletedIds.add(node.id);
|
|
23479
|
-
|
|
24264
|
+
changed2 = true;
|
|
23480
24265
|
}
|
|
23481
24266
|
}
|
|
23482
24267
|
}
|
|
@@ -23572,7 +24357,15 @@ function TreeNodeRow({
|
|
|
23572
24357
|
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-4 h-4 shrink-0" }),
|
|
23573
24358
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
23574
24359
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm truncate", children: title }),
|
|
23575
|
-
/* @__PURE__ */ jsxRuntime.
|
|
24360
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-xs text-faint min-w-0", children: [
|
|
24361
|
+
entry.computer && // A chat on a folder of the user's computer, in every list of chats, the browser's too.
|
|
24362
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { "data-testid": "session-computer", title: `On ${entry.computer}`, className: "inline-flex min-w-0 max-w-[50%] items-center gap-1", children: [
|
|
24363
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.LaptopIcon, { className: "w-3 h-3 shrink-0", "aria-hidden": "true" }),
|
|
24364
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: entry.computer })
|
|
24365
|
+
] }),
|
|
24366
|
+
entry.computer && subtitle && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
24367
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: subtitle })
|
|
24368
|
+
] })
|
|
23576
24369
|
] }),
|
|
23577
24370
|
isRunning && canStop && isChildSession && /* @__PURE__ */ jsxRuntime.jsx(
|
|
23578
24371
|
"button",
|
|
@@ -23669,9 +24462,10 @@ function SessionTreePanel({
|
|
|
23669
24462
|
}
|
|
23670
24463
|
const sessionList = listResult.status === "fulfilled" ? listResult.value : null;
|
|
23671
24464
|
const sessionTree = treeResult.status === "fulfilled" ? treeResult.value : null;
|
|
24465
|
+
const project = sessionTree?.nodes.some((node) => node.parentId === null && node.runKind === "project");
|
|
23672
24466
|
let nextNodes = mergeTreeNodes([
|
|
23673
24467
|
sessionList?.sessions.map(sessionToTreeNode) ?? [],
|
|
23674
|
-
sessionTree?.nodes ?? []
|
|
24468
|
+
project ? [] : sessionTree?.nodes ?? []
|
|
23675
24469
|
]);
|
|
23676
24470
|
if (pendingDeleteIds.current.size > 0) {
|
|
23677
24471
|
const stillReturned = /* @__PURE__ */ new Set();
|
|
@@ -23851,6 +24645,7 @@ exports.WhiteboardSurface = WhiteboardSurface;
|
|
|
23851
24645
|
exports.defaultSelectedMissionTaskId = defaultSelectedMissionTaskId;
|
|
23852
24646
|
exports.deriveMissionWorkerActivityLabel = deriveMissionWorkerActivityLabel;
|
|
23853
24647
|
exports.deriveOrbActivity = deriveOrbActivity;
|
|
24648
|
+
exports.forgetChatFiles = forgetChatFiles;
|
|
23854
24649
|
exports.formatMissionTimestamp = formatMissionTimestamp;
|
|
23855
24650
|
exports.groupMissionTasksByBucket = groupMissionTasksByBucket;
|
|
23856
24651
|
exports.isHiddenSimpleTool = isHiddenSimpleTool;
|
|
@@ -23868,6 +24663,7 @@ exports.missionTaskStatusDotClass = missionTaskStatusDotClass;
|
|
|
23868
24663
|
exports.missionTaskTitle = missionTaskTitle;
|
|
23869
24664
|
exports.missionWorkerTaskCounts = missionWorkerTaskCounts;
|
|
23870
24665
|
exports.persistChatViewMode = persistChatViewMode;
|
|
24666
|
+
exports.sessionComputer = sessionComputer;
|
|
23871
24667
|
exports.splitCitations = splitCitations;
|
|
23872
24668
|
exports.stripMissionControlMarkup = stripMissionControlMarkup;
|
|
23873
24669
|
exports.toolOrbState = toolOrbState;
|