@workerdeck/ui 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/build/{SessionPanel-CnU_IJ3-.d.mts → SessionPanel-13l25ubU.d.mts} +55 -7
- package/build/{SessionPanel-CHktI2CF.mjs → SessionPanel-DgwjH4Ve.mjs} +642 -231
- package/build/SessionPanel-DgwjH4Ve.mjs.map +1 -0
- package/build/index.d.mts +309 -53
- package/build/index.mjs +534 -397
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +3547 -0
- package/build/workspace.d.mts +10 -1
- package/build/workspace.mjs +4 -2
- package/build/workspace.mjs.map +1 -1
- package/package.json +15 -7
- package/src/components/agent/Composer.tsx +10 -10
- package/src/components/agent/ContextDialog.tsx +3 -2
- package/src/components/agent/Conversation.tsx +1 -1
- package/src/components/agent/McpDialog.tsx +3 -1
- package/src/components/agent/Scrubber.tsx +248 -0
- package/src/components/agent/SessionBrowser.tsx +113 -293
- package/src/components/agent/SessionItem.tsx +538 -0
- package/src/components/agent/SessionList.tsx +3 -1
- package/src/components/agent/SessionPanel.tsx +112 -21
- package/src/components/agent/SessionStatusIcon.tsx +43 -0
- package/src/components/agent/SessionSteps.tsx +118 -61
- package/src/components/agent/SessionWorkspace.tsx +11 -0
- package/src/components/agent/SkillsDialog.tsx +3 -2
- package/src/components/agent/StatusBar.tsx +1 -1
- package/src/components/agent/Transcript.tsx +147 -73
- package/src/components/agent/UsageDialog.tsx +3 -1
- package/src/components/agent/scrubber-marks.ts +277 -0
- package/src/components/terminal/PermissionPrompt.tsx +3 -0
- package/src/components/terminal/QuestionPrompt.tsx +3 -0
- package/src/components/terminal/StatusLine.tsx +3 -1
- package/src/components/ui/AlertDialog.tsx +23 -20
- package/src/components/ui/Dialog.tsx +26 -23
- package/src/components/ui/Menu.tsx +20 -17
- package/src/components/ui/PortalScope.tsx +27 -0
- package/src/components/ui/Select.tsx +19 -16
- package/src/components/ui/Tooltip.tsx +13 -10
- package/src/index.ts +5 -1
- package/src/styles/scoped.entry.css +16 -0
- package/src/styles/terminal.css +1 -1
- package/src/styles/theme.css +223 -8
- package/build/SessionPanel-CHktI2CF.mjs.map +0 -1
|
@@ -117,6 +117,33 @@ const Input = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("
|
|
|
117
117
|
}));
|
|
118
118
|
Input.displayName = "Input";
|
|
119
119
|
//#endregion
|
|
120
|
+
//#region src/components/ui/PortalScope.tsx
|
|
121
|
+
/**
|
|
122
|
+
* Re-establishes the `.wd-root` styling scope inside a portal.
|
|
123
|
+
*
|
|
124
|
+
* The scoped stylesheet (`@workerdeck/ui/scoped.css`) rewrites every rule to
|
|
125
|
+
* live under `.wd-root`, so an embedder's page keeps its own design system and
|
|
126
|
+
* the panel keeps this one. Base UI popups (Menu, Dialog, AlertDialog, Select,
|
|
127
|
+
* Tooltip) portal to `document.body` — OUTSIDE the embedder's wrapper — so
|
|
128
|
+
* without this they would render unstyled, or worse, styled by the host.
|
|
129
|
+
*
|
|
130
|
+
* `display: contents` (inline, so no stylesheet needs to load for it) makes the
|
|
131
|
+
* element generate no box: positioning, hit-testing and the popup's own layout
|
|
132
|
+
* are untouched. It still carries the class, which is all the scope needs —
|
|
133
|
+
* design tokens declared on `.wd-root` inherit through it, and every
|
|
134
|
+
* `.wd-root <x>` rule matches below it. Theme keeps working because the dark
|
|
135
|
+
* token block matches `[data-theme='dark']` on ANY ancestor, and `document.body`
|
|
136
|
+
* sits under the host's themed `<html>` just like the panel does.
|
|
137
|
+
*
|
|
138
|
+
* Under the classic `theme.css` integration the class matches no rule and the
|
|
139
|
+
* element is inert, so the wrapper is unconditional.
|
|
140
|
+
*/
|
|
141
|
+
const PortalScope = ({ children }) => /* @__PURE__ */ jsx("div", {
|
|
142
|
+
className: "wd-root",
|
|
143
|
+
style: { display: "contents" },
|
|
144
|
+
children
|
|
145
|
+
});
|
|
146
|
+
//#endregion
|
|
120
147
|
//#region src/components/ui/Select.tsx
|
|
121
148
|
const Select$1 = Select.Root;
|
|
122
149
|
const SelectValue = Select.Value;
|
|
@@ -130,7 +157,7 @@ const SelectTrigger = ({ className, children, ...props }) => /* @__PURE__ */ jsx
|
|
|
130
157
|
children: /* @__PURE__ */ jsx(ChevronsUpDown, { className: "size-3.5" })
|
|
131
158
|
})]
|
|
132
159
|
});
|
|
133
|
-
const SelectContent = ({ className, align = "start", alignItemWithTrigger = false, side = "bottom", sideOffset = 6, ...props }) => /* @__PURE__ */ jsx(Select.Portal, { children: /* @__PURE__ */ jsx(Select.Positioner, {
|
|
160
|
+
const SelectContent = ({ className, align = "start", alignItemWithTrigger = false, side = "bottom", sideOffset = 6, ...props }) => /* @__PURE__ */ jsx(Select.Portal, { children: /* @__PURE__ */ jsx(PortalScope, { children: /* @__PURE__ */ jsx(Select.Positioner, {
|
|
134
161
|
align,
|
|
135
162
|
alignItemWithTrigger,
|
|
136
163
|
side,
|
|
@@ -141,7 +168,7 @@ const SelectContent = ({ className, align = "start", alignItemWithTrigger = fals
|
|
|
141
168
|
className: cn("max-h-[min(24rem,var(--available-height))] min-w-[var(--anchor-width)] overflow-y-auto", "rounded-md border border-border bg-surface p-1 text-fg-1 shadow-(--shadow-lg) outline-none", className),
|
|
142
169
|
...props
|
|
143
170
|
})
|
|
144
|
-
}) });
|
|
171
|
+
}) }) });
|
|
145
172
|
const SelectItem = ({ className, children, ...props }) => /* @__PURE__ */ jsxs(Select.Item, {
|
|
146
173
|
"data-slot": "select-item",
|
|
147
174
|
className: cn("flex cursor-pointer items-start gap-2 rounded-sm px-2 py-1.5 text-body-sm text-text outline-none select-none", "data-highlighted:bg-surface-hover", className),
|
|
@@ -158,7 +185,7 @@ const SelectItem = ({ className, children, ...props }) => /* @__PURE__ */ jsxs(S
|
|
|
158
185
|
//#region src/components/ui/Menu.tsx
|
|
159
186
|
const Menu$1 = Menu.Root;
|
|
160
187
|
const MenuTrigger = Menu.Trigger;
|
|
161
|
-
const MenuContent = ({ className, align = "end", side = "bottom", sideOffset = 6, ...props }) => /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, {
|
|
188
|
+
const MenuContent = ({ className, align = "end", side = "bottom", sideOffset = 6, ...props }) => /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(PortalScope, { children: /* @__PURE__ */ jsx(Menu.Positioner, {
|
|
162
189
|
align,
|
|
163
190
|
side,
|
|
164
191
|
sideOffset,
|
|
@@ -168,7 +195,7 @@ const MenuContent = ({ className, align = "end", side = "bottom", sideOffset = 6
|
|
|
168
195
|
className: cn("min-w-48 rounded-md border border-border bg-surface p-1 text-fg-1 shadow-(--shadow-lg) outline-none", "transition-[opacity,transform] duration-(--motion-base)", "data-starting-style:scale-95 data-starting-style:opacity-0", "data-ending-style:scale-95 data-ending-style:opacity-0", className),
|
|
169
196
|
...props
|
|
170
197
|
})
|
|
171
|
-
}) });
|
|
198
|
+
}) }) });
|
|
172
199
|
const MenuItem = ({ className, destructive, ...props }) => /* @__PURE__ */ jsx(Menu.Item, {
|
|
173
200
|
"data-slot": "menu-item",
|
|
174
201
|
className: cn("flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-body-sm outline-none select-none", "data-highlighted:bg-surface-hover", destructive ? "text-danger" : "text-text", className),
|
|
@@ -190,12 +217,12 @@ const DialogClose = Dialog.Close;
|
|
|
190
217
|
* Taller than {@link AlertDialogContent} and scrollable inside, because these
|
|
191
218
|
* carry lists whose length is the engine's business, not the layout's.
|
|
192
219
|
*/
|
|
193
|
-
const DialogContent = ({ className, children, size = "md", ...props }) => /* @__PURE__ */
|
|
220
|
+
const DialogContent = ({ className, children, size = "md", ...props }) => /* @__PURE__ */ jsx(Dialog.Portal, { children: /* @__PURE__ */ jsxs(PortalScope, { children: [/* @__PURE__ */ jsx(Dialog.Backdrop, { className: cn("fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]", "transition-opacity duration-(--motion-base)", "data-starting-style:opacity-0 data-ending-style:opacity-0") }), /* @__PURE__ */ jsx(Dialog.Popup, {
|
|
194
221
|
"data-slot": "dialog-content",
|
|
195
222
|
className: cn("fixed top-1/2 left-1/2 z-70 flex max-h-[min(42rem,calc(100dvh-3rem))] -translate-x-1/2 -translate-y-1/2 flex-col", size === "sm" && "w-[min(24rem,calc(100vw-2rem))]", size === "md" && "w-[min(32rem,calc(100vw-2rem))]", size === "lg" && "w-[min(46rem,calc(100vw-2rem))]", "rounded-lg border border-border bg-surface shadow-(--shadow-lg) outline-none", "transition-[opacity,transform] duration-(--motion-base)", "data-starting-style:scale-95 data-starting-style:opacity-0", "data-ending-style:scale-95 data-ending-style:opacity-0", className),
|
|
196
223
|
...props,
|
|
197
224
|
children
|
|
198
|
-
})] });
|
|
225
|
+
})] }) });
|
|
199
226
|
/** Title row with the close button, pinned above the scrolling body. */
|
|
200
227
|
const DialogHeader = ({ title, description, actions }) => /* @__PURE__ */ jsxs("div", {
|
|
201
228
|
className: "flex items-start gap-2 border-b border-border px-4 py-3",
|
|
@@ -237,7 +264,7 @@ const DialogRow = ({ label, children, mono }) => /* @__PURE__ */ jsxs("div", {
|
|
|
237
264
|
//#endregion
|
|
238
265
|
//#region src/components/ui/Tooltip.tsx
|
|
239
266
|
const TooltipProvider = Tooltip.Provider;
|
|
240
|
-
const TooltipContent = ({ className, side = "top", sideOffset = 6, ...props }) => /* @__PURE__ */ jsx(Tooltip.Portal, { children: /* @__PURE__ */ jsx(Tooltip.Positioner, {
|
|
267
|
+
const TooltipContent = ({ className, side = "top", sideOffset = 6, ...props }) => /* @__PURE__ */ jsx(Tooltip.Portal, { children: /* @__PURE__ */ jsx(PortalScope, { children: /* @__PURE__ */ jsx(Tooltip.Positioner, {
|
|
241
268
|
side,
|
|
242
269
|
sideOffset,
|
|
243
270
|
className: "isolate z-90",
|
|
@@ -246,7 +273,7 @@ const TooltipContent = ({ className, side = "top", sideOffset = 6, ...props }) =
|
|
|
246
273
|
className: cn("rounded-md border border-border bg-surface px-2 py-1 text-label text-fg-2 shadow-(--shadow-md) outline-none", className),
|
|
247
274
|
...props
|
|
248
275
|
})
|
|
249
|
-
}) });
|
|
276
|
+
}) }) });
|
|
250
277
|
/**
|
|
251
278
|
* Convenience wrapper: `<Tip content="..."><Button/></Tip>`.
|
|
252
279
|
*
|
|
@@ -6281,7 +6308,7 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
|
|
|
6281
6308
|
}
|
|
6282
6309
|
};
|
|
6283
6310
|
const errorRow = attachments?.error ? /* @__PURE__ */ jsxs("div", {
|
|
6284
|
-
className: cn("mx-auto mt-1 flex w-full max-w-[var(--wd-
|
|
6311
|
+
className: cn("mx-auto mt-1 flex w-full max-w-[var(--wd-transcript-max-width)] items-center gap-2 text-label text-danger"),
|
|
6285
6312
|
children: [
|
|
6286
6313
|
/* @__PURE__ */ jsx(TriangleAlert, { className: "size-3 shrink-0" }),
|
|
6287
6314
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -6354,10 +6381,10 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
|
|
|
6354
6381
|
}
|
|
6355
6382
|
return /* @__PURE__ */ jsxs("div", {
|
|
6356
6383
|
"data-slot": "composer",
|
|
6357
|
-
className: cn("px-
|
|
6384
|
+
className: cn("px-[var(--wd-composer-padding)] pb-[var(--wd-composer-padding)]", className),
|
|
6358
6385
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
6359
6386
|
...dropHandlers,
|
|
6360
|
-
className: cn("mx-auto w-full max-w-[var(--wd-
|
|
6387
|
+
className: cn("mx-auto w-full max-w-[var(--wd-transcript-max-width)] overflow-hidden border border-border bg-bg", "transition-colors rounded-lg shadow-(--shadow-xs)", "focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/30", dragging && "border-ring ring-2 ring-ring/30", disabled && "opacity-60"),
|
|
6361
6388
|
children: [staged.length > 0 && attachments ? /* @__PURE__ */ jsx(AttachmentStrip, { attachments }) : null, inline ? /* @__PURE__ */ jsxs("div", {
|
|
6362
6389
|
className: "flex items-end gap-1 p-1",
|
|
6363
6390
|
children: [
|
|
@@ -6372,7 +6399,7 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
|
|
|
6372
6399
|
minHeight: 20,
|
|
6373
6400
|
maxHeight: 192,
|
|
6374
6401
|
"aria-label": "Message the agent",
|
|
6375
|
-
className: "min-w-0 flex-1 py-
|
|
6402
|
+
className: "min-w-0 flex-1 py-1 text-body-sm text-text",
|
|
6376
6403
|
onImagePaste: (file) => attachments?.add([file])
|
|
6377
6404
|
}),
|
|
6378
6405
|
submitButton
|
|
@@ -6387,7 +6414,7 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
|
|
|
6387
6414
|
minHeight: 28,
|
|
6388
6415
|
maxHeight: 192,
|
|
6389
6416
|
"aria-label": "Message the agent",
|
|
6390
|
-
className: "px-3 pt-2.5 pb-
|
|
6417
|
+
className: "px-3 pt-2.5 pb-0 text-body-sm text-text",
|
|
6391
6418
|
onImagePaste: (file) => attachments?.add([file])
|
|
6392
6419
|
}), /* @__PURE__ */ jsxs("div", {
|
|
6393
6420
|
className: "flex items-center justify-between gap-2 px-2 pb-2",
|
|
@@ -6494,81 +6521,84 @@ const usageTint$1 = (pct) => pct >= 90 ? "bg-danger" : pct >= 70 ? "bg-warning"
|
|
|
6494
6521
|
* are different questions asked at different moments, so they are different
|
|
6495
6522
|
* screens rather than one "details" list you scroll past two answers to reach.
|
|
6496
6523
|
*/
|
|
6497
|
-
function ContextDialog({ usage, open, onOpenChange }) {
|
|
6524
|
+
function ContextDialog({ usage, open, onOpenChange, className }) {
|
|
6498
6525
|
return /* @__PURE__ */ jsx(Dialog$1, {
|
|
6499
6526
|
open,
|
|
6500
6527
|
onOpenChange,
|
|
6501
|
-
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
className: "
|
|
6512
|
-
children: "
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
className:
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
className: "
|
|
6537
|
-
children: "
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
children: [/* @__PURE__ */ jsxs("
|
|
6546
|
-
className: "flex
|
|
6547
|
-
children: [/* @__PURE__ */
|
|
6548
|
-
className: "
|
|
6549
|
-
|
|
6528
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
6529
|
+
className,
|
|
6530
|
+
children: [/* @__PURE__ */ jsx(DialogHeader, {
|
|
6531
|
+
title: "Context",
|
|
6532
|
+
description: usage?.model
|
|
6533
|
+
}), /* @__PURE__ */ jsx(DialogBody, { children: !usage ? /* @__PURE__ */ jsx("p", {
|
|
6534
|
+
className: "py-6 text-center text-body-sm text-fg-4",
|
|
6535
|
+
children: "No reading yet — the context window is measured after a turn completes."
|
|
6536
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
6537
|
+
/* @__PURE__ */ jsxs("div", {
|
|
6538
|
+
className: "flex items-baseline justify-between gap-4",
|
|
6539
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
6540
|
+
className: "text-label text-fg-3",
|
|
6541
|
+
children: "Used"
|
|
6542
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
6543
|
+
className: "font-mono text-body-sm text-fg-1",
|
|
6544
|
+
children: [
|
|
6545
|
+
formatTokens(usage.totalTokens),
|
|
6546
|
+
" / ",
|
|
6547
|
+
formatTokens(usage.maxTokens),
|
|
6548
|
+
" ·",
|
|
6549
|
+
" ",
|
|
6550
|
+
usage.percentage.toFixed(0),
|
|
6551
|
+
"%"
|
|
6552
|
+
]
|
|
6553
|
+
})]
|
|
6554
|
+
}),
|
|
6555
|
+
/* @__PURE__ */ jsx("div", {
|
|
6556
|
+
className: "mt-2 h-2 overflow-hidden rounded-full bg-border",
|
|
6557
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
6558
|
+
className: cn("h-full rounded-full", usageTint$1(usage.percentage)),
|
|
6559
|
+
style: { width: `${Math.min(100, Math.max(2, usage.percentage))}%` }
|
|
6560
|
+
})
|
|
6561
|
+
}),
|
|
6562
|
+
usage.categories.length > 0 ? /* @__PURE__ */ jsxs("div", {
|
|
6563
|
+
className: "mt-5",
|
|
6564
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
6565
|
+
className: "text-label font-medium text-fg-3",
|
|
6566
|
+
children: "Breakdown"
|
|
6567
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
6568
|
+
className: "mt-2 flex flex-col gap-3",
|
|
6569
|
+
children: usage.categories.map((category) => {
|
|
6570
|
+
const share = category.tokens / Math.max(usage.maxTokens, 1) * 100;
|
|
6571
|
+
const color = cssColor$1(category.color);
|
|
6572
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
6573
|
+
className: "flex items-baseline justify-between gap-3",
|
|
6574
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
6575
|
+
className: "flex min-w-0 items-center gap-2",
|
|
6576
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
6577
|
+
className: "size-2 shrink-0 rounded-full bg-fg-4",
|
|
6578
|
+
style: color ? { backgroundColor: color } : void 0
|
|
6579
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
6580
|
+
className: "truncate text-body-sm text-fg-1",
|
|
6581
|
+
children: category.name
|
|
6582
|
+
})]
|
|
6550
6583
|
}), /* @__PURE__ */ jsx("span", {
|
|
6551
|
-
className: "
|
|
6552
|
-
children: category.
|
|
6584
|
+
className: "shrink-0 font-mono text-label text-fg-3",
|
|
6585
|
+
children: formatTokens(category.tokens)
|
|
6553
6586
|
})]
|
|
6554
|
-
}), /* @__PURE__ */ jsx("
|
|
6555
|
-
className: "
|
|
6556
|
-
children:
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
})]
|
|
6570
|
-
}) : null
|
|
6571
|
-
] }) })] })
|
|
6587
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
6588
|
+
className: "mt-1 h-1 overflow-hidden rounded-full bg-border",
|
|
6589
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
6590
|
+
className: "h-full rounded-full bg-fg-4",
|
|
6591
|
+
style: {
|
|
6592
|
+
width: `${Math.min(100, share)}%`,
|
|
6593
|
+
backgroundColor: color
|
|
6594
|
+
}
|
|
6595
|
+
})
|
|
6596
|
+
})] }, category.name);
|
|
6597
|
+
})
|
|
6598
|
+
})]
|
|
6599
|
+
}) : null
|
|
6600
|
+
] }) })]
|
|
6601
|
+
})
|
|
6572
6602
|
});
|
|
6573
6603
|
}
|
|
6574
6604
|
//#endregion
|
|
@@ -6765,7 +6795,7 @@ const STATUS_VARIANT = {
|
|
|
6765
6795
|
* the tool view either renders it or says why it can't, rather than leaving a
|
|
6766
6796
|
* silent gap or claiming the absence is universal.
|
|
6767
6797
|
*/
|
|
6768
|
-
function McpDialog({ client, sessionId, open, onOpenChange, canManageServers = true }) {
|
|
6798
|
+
function McpDialog({ client, sessionId, open, onOpenChange, canManageServers = true, className }) {
|
|
6769
6799
|
const [servers, setServers] = useState();
|
|
6770
6800
|
const [error, setError] = useState();
|
|
6771
6801
|
const [loading, setLoading] = useState(false);
|
|
@@ -6807,35 +6837,38 @@ function McpDialog({ client, sessionId, open, onOpenChange, canManageServers = t
|
|
|
6807
6837
|
return /* @__PURE__ */ jsx(Dialog$1, {
|
|
6808
6838
|
open,
|
|
6809
6839
|
onOpenChange,
|
|
6810
|
-
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
children: error
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6840
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
6841
|
+
className,
|
|
6842
|
+
children: [/* @__PURE__ */ jsx(DialogHeader, {
|
|
6843
|
+
title: tool?.name ?? server?.name ?? "MCP servers",
|
|
6844
|
+
description: tool ? `${server?.name} tool` : server ? server.serverInfo?.name : void 0,
|
|
6845
|
+
actions: selectedServer ? /* @__PURE__ */ jsxs(Button, {
|
|
6846
|
+
variant: "ghost",
|
|
6847
|
+
size: "xs",
|
|
6848
|
+
onClick: () => selectedTool ? setSelectedTool(void 0) : setSelectedServer(void 0),
|
|
6849
|
+
children: [/* @__PURE__ */ jsx(ChevronLeft, { className: "size-3.5" }), "Back"]
|
|
6850
|
+
}) : /* @__PURE__ */ jsxs(Button, {
|
|
6851
|
+
variant: "ghost",
|
|
6852
|
+
size: "xs",
|
|
6853
|
+
onClick: () => void load(),
|
|
6854
|
+
disabled: loading,
|
|
6855
|
+
children: [loading ? /* @__PURE__ */ jsx(Spinner, { className: "size-3 text-current" }) : /* @__PURE__ */ jsx(RotateCw, { className: "size-3" }), "Refresh"]
|
|
6856
|
+
})
|
|
6857
|
+
}), /* @__PURE__ */ jsxs(DialogBody, { children: [error ? /* @__PURE__ */ jsx("div", {
|
|
6858
|
+
className: "mb-3 rounded-md bg-danger-bg px-3 py-2 text-body-sm text-danger",
|
|
6859
|
+
children: error
|
|
6860
|
+
}) : null, tool ? /* @__PURE__ */ jsx(ToolView, { tool }) : server ? /* @__PURE__ */ jsx(ServerView, {
|
|
6861
|
+
server,
|
|
6862
|
+
busy: busyServer === server.name,
|
|
6863
|
+
canManage: canManageServers,
|
|
6864
|
+
onAct: (action) => void act(server.name, action),
|
|
6865
|
+
onSelectTool: setSelectedTool
|
|
6866
|
+
}) : error && !servers ? null : /* @__PURE__ */ jsx(ServerList, {
|
|
6867
|
+
servers,
|
|
6868
|
+
loading,
|
|
6869
|
+
onSelect: setSelectedServer
|
|
6870
|
+
})] })]
|
|
6871
|
+
})
|
|
6839
6872
|
});
|
|
6840
6873
|
}
|
|
6841
6874
|
function ServerList({ servers, loading, onSelect }) {
|
|
@@ -7035,7 +7068,7 @@ const SCOPE_LABEL = {
|
|
|
7035
7068
|
* Fed from the session's `skills` event rather than a REST route, because that
|
|
7036
7069
|
* is the channel the engine refreshes on its own when a skill changes on disk.
|
|
7037
7070
|
*/
|
|
7038
|
-
function SkillsDialog({ skills, open, onOpenChange, onUse }) {
|
|
7071
|
+
function SkillsDialog({ skills, open, onOpenChange, onUse, className }) {
|
|
7039
7072
|
const [selected, setSelected] = useState();
|
|
7040
7073
|
const skill = skills?.find((s) => s.name === selected);
|
|
7041
7074
|
return /* @__PURE__ */ jsx(Dialog$1, {
|
|
@@ -7044,23 +7077,26 @@ function SkillsDialog({ skills, open, onOpenChange, onUse }) {
|
|
|
7044
7077
|
if (!next) setSelected(void 0);
|
|
7045
7078
|
onOpenChange(next);
|
|
7046
7079
|
},
|
|
7047
|
-
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7080
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
7081
|
+
className,
|
|
7082
|
+
children: [/* @__PURE__ */ jsx(DialogHeader, {
|
|
7083
|
+
title: skill ? skill.displayName ?? skill.name : "Skills",
|
|
7084
|
+
description: skill ? skill.name !== (skill.displayName ?? skill.name) ? skill.name : void 0 : "Capabilities the agent can choose to use. Not commands — the model picks them from their descriptions.",
|
|
7085
|
+
actions: skill ? /* @__PURE__ */ jsxs(Button, {
|
|
7086
|
+
variant: "ghost",
|
|
7087
|
+
size: "xs",
|
|
7088
|
+
onClick: () => setSelected(void 0),
|
|
7089
|
+
children: [/* @__PURE__ */ jsx(ChevronLeft, { className: "size-3.5" }), "Back"]
|
|
7090
|
+
}) : void 0
|
|
7091
|
+
}), /* @__PURE__ */ jsx(DialogBody, { children: skill ? /* @__PURE__ */ jsx(SkillView, {
|
|
7092
|
+
skill,
|
|
7093
|
+
onUse,
|
|
7094
|
+
onUsed: () => onOpenChange(false)
|
|
7095
|
+
}) : /* @__PURE__ */ jsx(SkillList, {
|
|
7096
|
+
skills,
|
|
7097
|
+
onSelect: setSelected
|
|
7098
|
+
}) })]
|
|
7099
|
+
})
|
|
7064
7100
|
});
|
|
7065
7101
|
}
|
|
7066
7102
|
function SkillList({ skills, onSelect }) {
|
|
@@ -7940,7 +7976,7 @@ function TabStrip({ tabs, active, onSelect }) {
|
|
|
7940
7976
|
}
|
|
7941
7977
|
//#endregion
|
|
7942
7978
|
//#region src/components/terminal/PermissionPrompt.tsx
|
|
7943
|
-
function TerminalPermissionPrompt({ request, onApprove, onDeny }) {
|
|
7979
|
+
function TerminalPermissionPrompt({ request, onApprove, onDeny, className }) {
|
|
7944
7980
|
const [focused, setFocused] = useState(0);
|
|
7945
7981
|
const [denying, setDenying] = useState(false);
|
|
7946
7982
|
const [reason, setReason] = useState("");
|
|
@@ -7978,6 +8014,7 @@ function TerminalPermissionPrompt({ request, onApprove, onDeny }) {
|
|
|
7978
8014
|
];
|
|
7979
8015
|
return /* @__PURE__ */ jsxs("div", {
|
|
7980
8016
|
"data-slot": "permission-prompt",
|
|
8017
|
+
className,
|
|
7981
8018
|
onKeyDown: (event) => {
|
|
7982
8019
|
if (event.key === "Escape") {
|
|
7983
8020
|
event.preventDefault();
|
|
@@ -8035,7 +8072,7 @@ function answerFor(selection) {
|
|
|
8035
8072
|
if (selection.otherActive && selection.other.trim()) parts.push(selection.other.trim());
|
|
8036
8073
|
return parts.join(", ");
|
|
8037
8074
|
}
|
|
8038
|
-
function TerminalQuestionPrompt({ request, onAnswer, onDismiss }) {
|
|
8075
|
+
function TerminalQuestionPrompt({ request, onAnswer, onDismiss, className }) {
|
|
8039
8076
|
const questions = parseUserQuestions(request.input);
|
|
8040
8077
|
const [selections, setSelections] = useState(() => questions.map(() => EMPTY));
|
|
8041
8078
|
const [cursors, setCursors] = useState(() => questions.map(() => 0));
|
|
@@ -8077,6 +8114,7 @@ function TerminalQuestionPrompt({ request, onAnswer, onDismiss }) {
|
|
|
8077
8114
|
const move = (delta) => setTab((current) => Math.min(questions.length, Math.max(0, current + delta)));
|
|
8078
8115
|
return /* @__PURE__ */ jsxs("div", {
|
|
8079
8116
|
"data-slot": "question-prompt",
|
|
8117
|
+
className,
|
|
8080
8118
|
onKeyDown: (event) => {
|
|
8081
8119
|
if (event.key === "Escape") {
|
|
8082
8120
|
event.preventDefault();
|
|
@@ -8576,7 +8614,7 @@ function StatusBar({ state, rateLimits, connected, connection, onOpenStatus, onO
|
|
|
8576
8614
|
const terminal = useTranscriptVariant() === "terminal";
|
|
8577
8615
|
return /* @__PURE__ */ jsxs("div", {
|
|
8578
8616
|
"data-slot": "status-bar",
|
|
8579
|
-
className: cn("flex h-[
|
|
8617
|
+
className: cn("flex h-[var(--wd-status-bar-height)] items-baseline gap-2 border-border bg-surface p-1.5", placement === "bottom" ? terminal ? void 0 : "border-t" : "border-b", className),
|
|
8580
8618
|
children: [
|
|
8581
8619
|
/* @__PURE__ */ jsx(Slot, {
|
|
8582
8620
|
onClick: onOpenStatus,
|
|
@@ -8650,7 +8688,7 @@ function Conversation({ className, children, ...props }) {
|
|
|
8650
8688
|
function ConversationContent({ className, children, ...props }) {
|
|
8651
8689
|
return /* @__PURE__ */ jsx(StickToBottom.Content, {
|
|
8652
8690
|
"data-slot": "conversation-content",
|
|
8653
|
-
className: cn("mx-auto flex w-full max-w-[var(--wd-
|
|
8691
|
+
className: cn("mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] flex-col gap-4 px-4 py-4", className),
|
|
8654
8692
|
...props,
|
|
8655
8693
|
children
|
|
8656
8694
|
});
|
|
@@ -9845,7 +9883,7 @@ function blockHeight(block, m) {
|
|
|
9845
9883
|
//#region src/components/terminal/scrubber.tsx
|
|
9846
9884
|
/** The member closest to a rail-space y — what a press or peek on a merged
|
|
9847
9885
|
* cluster resolves to. */
|
|
9848
|
-
function nearestMember(cluster, y) {
|
|
9886
|
+
function nearestMember$1(cluster, y) {
|
|
9849
9887
|
let best;
|
|
9850
9888
|
for (const member of cluster.marks) if (!best || Math.abs(member.y - y) < Math.abs(best.y - y)) best = member;
|
|
9851
9889
|
return best?.mark;
|
|
@@ -9865,7 +9903,7 @@ function nearestMember(cluster, y) {
|
|
|
9865
9903
|
* It also buys the marks their width back: three lanes in a 16px rail is 5px a
|
|
9866
9904
|
* lane, which is a hard target to hit and a hard colour to see.
|
|
9867
9905
|
*/
|
|
9868
|
-
const LANE = {
|
|
9906
|
+
const LANE$1 = {
|
|
9869
9907
|
user: "l",
|
|
9870
9908
|
subagent: "l",
|
|
9871
9909
|
turn: "r",
|
|
@@ -9877,7 +9915,7 @@ const LANE = {
|
|
|
9877
9915
|
bookmark: "f"
|
|
9878
9916
|
};
|
|
9879
9917
|
/** Who wins the colour when marks merge. */
|
|
9880
|
-
const LOUDNESS = {
|
|
9918
|
+
const LOUDNESS$1 = {
|
|
9881
9919
|
approval: 7,
|
|
9882
9920
|
error: 6,
|
|
9883
9921
|
turnFailed: 5,
|
|
@@ -9888,7 +9926,7 @@ const LOUDNESS = {
|
|
|
9888
9926
|
subagent: 1,
|
|
9889
9927
|
recap: 0
|
|
9890
9928
|
};
|
|
9891
|
-
const KIND_NAME = {
|
|
9929
|
+
const KIND_NAME$1 = {
|
|
9892
9930
|
user: "you",
|
|
9893
9931
|
subagent: "sub-agent",
|
|
9894
9932
|
turn: "response · turn end",
|
|
@@ -9899,7 +9937,7 @@ const KIND_NAME = {
|
|
|
9899
9937
|
recap: "catch-up boundary",
|
|
9900
9938
|
bookmark: "bookmark"
|
|
9901
9939
|
};
|
|
9902
|
-
const MIN_MARK = 2;
|
|
9940
|
+
const MIN_MARK$1 = 2;
|
|
9903
9941
|
/**
|
|
9904
9942
|
* Rail pixels per content pixel — the one scale both the marks and the viewport
|
|
9905
9943
|
* band are drawn at, so they cannot disagree about where a row sits.
|
|
@@ -9923,15 +9961,15 @@ const MIN_MARK = 2;
|
|
|
9923
9961
|
function railScale(railH, totalSize, viewportH) {
|
|
9924
9962
|
return totalSize > 0 ? railH / Math.max(totalSize, viewportH) : 0;
|
|
9925
9963
|
}
|
|
9926
|
-
const doneLine = (turn) => `${turn.isError ? turn.subtype : "done"} · ${formatDuration(turn.durationMs)} · ${formatCost(turn.totalCostUsd)}`;
|
|
9927
|
-
function excerpt(item) {
|
|
9964
|
+
const doneLine$1 = (turn) => `${turn.isError ? turn.subtype : "done"} · ${formatDuration(turn.durationMs)} · ${formatCost(turn.totalCostUsd)}`;
|
|
9965
|
+
function excerpt$1(item) {
|
|
9928
9966
|
switch (item.kind) {
|
|
9929
9967
|
case "user":
|
|
9930
9968
|
case "assistant_text":
|
|
9931
9969
|
case "thinking":
|
|
9932
9970
|
case "notice": return item.text;
|
|
9933
9971
|
case "tool_call": return `${item.name}(${toolInputPreview(item.input)})`;
|
|
9934
|
-
case "turn_result": return doneLine(item);
|
|
9972
|
+
case "turn_result": return doneLine$1(item);
|
|
9935
9973
|
case "file_delivered": return item.path;
|
|
9936
9974
|
default: return "";
|
|
9937
9975
|
}
|
|
@@ -10023,9 +10061,9 @@ function buildClusters(props, railH) {
|
|
|
10023
10061
|
for (const mark of marks) {
|
|
10024
10062
|
const within = mark.itemIndex >= 0 ? positionInRow?.(mark.itemIndex) : void 0;
|
|
10025
10063
|
const rowH = sizeOfRow(mark.rowIndex);
|
|
10026
|
-
const h = within ? MIN_MARK : Math.max(MIN_MARK, Math.round(rowH * scale));
|
|
10064
|
+
const h = within ? MIN_MARK$1 : Math.max(MIN_MARK$1, Math.round(rowH * scale));
|
|
10027
10065
|
const y = Math.min(Math.max(0, railH - h), Math.round((offsetOfRow(mark.rowIndex) + (within ? within.ordinal / within.count * rowH : 0)) * scale));
|
|
10028
|
-
const lane = LANE[mark.kind];
|
|
10066
|
+
const lane = LANE$1[mark.kind];
|
|
10029
10067
|
const list = lanes.get(lane) ?? [];
|
|
10030
10068
|
list.push({
|
|
10031
10069
|
mark,
|
|
@@ -10040,7 +10078,7 @@ function buildClusters(props, railH) {
|
|
|
10040
10078
|
let current = null;
|
|
10041
10079
|
for (const { mark, y, h } of list) if (current && y <= current.y + current.h + 1) {
|
|
10042
10080
|
current.h = Math.max(current.h, y + h - current.y);
|
|
10043
|
-
if (LOUDNESS[mark.kind] > LOUDNESS[current.kind]) current.kind = mark.kind;
|
|
10081
|
+
if (LOUDNESS$1[mark.kind] > LOUDNESS$1[current.kind]) current.kind = mark.kind;
|
|
10044
10082
|
current.marks.push({
|
|
10045
10083
|
mark,
|
|
10046
10084
|
y
|
|
@@ -10060,15 +10098,15 @@ function buildClusters(props, railH) {
|
|
|
10060
10098
|
}
|
|
10061
10099
|
}
|
|
10062
10100
|
if (pendingApprovals.length > 0) clusters.push({
|
|
10063
|
-
lane: LANE.approval,
|
|
10101
|
+
lane: LANE$1.approval,
|
|
10064
10102
|
kind: "approval",
|
|
10065
|
-
y: Math.max(0, railH - MIN_MARK),
|
|
10066
|
-
h: MIN_MARK,
|
|
10103
|
+
y: Math.max(0, railH - MIN_MARK$1),
|
|
10104
|
+
h: MIN_MARK$1,
|
|
10067
10105
|
marks: []
|
|
10068
10106
|
});
|
|
10069
10107
|
return clusters;
|
|
10070
10108
|
}
|
|
10071
|
-
function peekContent(cluster, first, { items, pendingApprovals, recapRow }) {
|
|
10109
|
+
function peekContent$1(cluster, first, { items, pendingApprovals, recapRow }) {
|
|
10072
10110
|
const more = cluster.marks.length > 1 ? ` · ${cluster.marks.length} marks` : "";
|
|
10073
10111
|
let body = null;
|
|
10074
10112
|
if (cluster.kind === "approval") {
|
|
@@ -10102,7 +10140,7 @@ function peekContent(cluster, first, { items, pendingApprovals, recapRow }) {
|
|
|
10102
10140
|
}), item.text]
|
|
10103
10141
|
}) : null, turn?.kind === "turn_result" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
10104
10142
|
"data-tone": turn.isError ? "red" : "faint",
|
|
10105
|
-
children: doneLine(turn)
|
|
10143
|
+
children: doneLine$1(turn)
|
|
10106
10144
|
}), turn.errors?.map((message, index) => /* @__PURE__ */ jsx("div", {
|
|
10107
10145
|
"data-tone": "red",
|
|
10108
10146
|
children: message
|
|
@@ -10115,7 +10153,7 @@ function peekContent(cluster, first, { items, pendingApprovals, recapRow }) {
|
|
|
10115
10153
|
children: [first.kind === "user" ? /* @__PURE__ */ jsx("span", {
|
|
10116
10154
|
"data-tone": "dim",
|
|
10117
10155
|
children: "❯ "
|
|
10118
|
-
}) : null, excerpt(item)]
|
|
10156
|
+
}) : null, excerpt$1(item)]
|
|
10119
10157
|
}), failure ? /* @__PURE__ */ jsx("div", {
|
|
10120
10158
|
className: "term-scrub-ex",
|
|
10121
10159
|
"data-tone": "red",
|
|
@@ -10125,7 +10163,7 @@ function peekContent(cluster, first, { items, pendingApprovals, recapRow }) {
|
|
|
10125
10163
|
}
|
|
10126
10164
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
10127
10165
|
"data-tone": "faint",
|
|
10128
|
-
children: [KIND_NAME[first?.kind ?? cluster.kind], more]
|
|
10166
|
+
children: [KIND_NAME$1[first?.kind ?? cluster.kind], more]
|
|
10129
10167
|
}), body] });
|
|
10130
10168
|
}
|
|
10131
10169
|
function TerminalScrubber(props) {
|
|
@@ -10202,12 +10240,12 @@ function TerminalScrubber(props) {
|
|
|
10202
10240
|
stick.scrollToBottom();
|
|
10203
10241
|
return;
|
|
10204
10242
|
}
|
|
10205
|
-
const mark = nearestMember(cluster, railY(clientY));
|
|
10243
|
+
const mark = nearestMember$1(cluster, railY(clientY));
|
|
10206
10244
|
if (mark) onJumpToRow(mark.rowIndex);
|
|
10207
10245
|
};
|
|
10208
10246
|
const showPeek = (cluster, clientY) => {
|
|
10209
10247
|
const y = railY(clientY);
|
|
10210
|
-
const mark = nearestMember(cluster, y);
|
|
10248
|
+
const mark = nearestMember$1(cluster, y);
|
|
10211
10249
|
setPeek((previous) => previous && previous.cluster === cluster && previous.mark === mark ? previous : {
|
|
10212
10250
|
cluster,
|
|
10213
10251
|
mark,
|
|
@@ -10288,13 +10326,328 @@ function TerminalScrubber(props) {
|
|
|
10288
10326
|
ref: peekRef,
|
|
10289
10327
|
className: "term-scrub-peek",
|
|
10290
10328
|
style: { top: peek.y },
|
|
10291
|
-
children: peekContent(peek.cluster, peek.mark, props)
|
|
10329
|
+
children: peekContent$1(peek.cluster, peek.mark, props)
|
|
10292
10330
|
}) : null
|
|
10293
10331
|
]
|
|
10294
10332
|
})
|
|
10295
10333
|
});
|
|
10296
10334
|
}
|
|
10297
10335
|
//#endregion
|
|
10336
|
+
//#region src/components/agent/scrubber-marks.ts
|
|
10337
|
+
/** The member closest to a rail-space y — what a press or peek on a merged
|
|
10338
|
+
* cluster resolves to. */
|
|
10339
|
+
function nearestMember(cluster, y) {
|
|
10340
|
+
let best;
|
|
10341
|
+
for (const member of cluster.marks) if (!best || Math.abs(member.y - y) < Math.abs(best.y - y)) best = member;
|
|
10342
|
+
return best?.mark;
|
|
10343
|
+
}
|
|
10344
|
+
/** The two lanes are channels, not classes: left is what went *in* (your
|
|
10345
|
+
* prompts, the sub-agents you dispatched), right is what came *out* (each
|
|
10346
|
+
* turn's answer, and everything that went wrong producing one). Full width is
|
|
10347
|
+
* for what is not a channel at all: the waiting approval, a bookmark, the
|
|
10348
|
+
* catch-up seam. See the terminal scrubber for the full argument. */
|
|
10349
|
+
const LANE = {
|
|
10350
|
+
user: "l",
|
|
10351
|
+
subagent: "l",
|
|
10352
|
+
turn: "r",
|
|
10353
|
+
turnFailed: "r",
|
|
10354
|
+
toolFailed: "r",
|
|
10355
|
+
error: "r",
|
|
10356
|
+
approval: "f",
|
|
10357
|
+
recap: "f",
|
|
10358
|
+
bookmark: "f"
|
|
10359
|
+
};
|
|
10360
|
+
/** Who wins the colour when marks merge. */
|
|
10361
|
+
const LOUDNESS = {
|
|
10362
|
+
approval: 7,
|
|
10363
|
+
error: 6,
|
|
10364
|
+
turnFailed: 5,
|
|
10365
|
+
toolFailed: 4,
|
|
10366
|
+
user: 3,
|
|
10367
|
+
turn: 2,
|
|
10368
|
+
bookmark: 1,
|
|
10369
|
+
subagent: 1,
|
|
10370
|
+
recap: 0
|
|
10371
|
+
};
|
|
10372
|
+
const KIND_NAME = {
|
|
10373
|
+
user: "you",
|
|
10374
|
+
subagent: "sub-agent",
|
|
10375
|
+
turn: "response · turn end",
|
|
10376
|
+
turnFailed: "turn failed",
|
|
10377
|
+
toolFailed: "tool failed",
|
|
10378
|
+
error: "error",
|
|
10379
|
+
approval: "pending approval",
|
|
10380
|
+
recap: "catch-up boundary",
|
|
10381
|
+
bookmark: "bookmark"
|
|
10382
|
+
};
|
|
10383
|
+
const doneLine = (turn) => `${turn.isError ? turn.subtype : "done"} · ${formatDuration(turn.durationMs)} · ${formatCost(turn.totalCostUsd)}`;
|
|
10384
|
+
function excerpt(item) {
|
|
10385
|
+
switch (item.kind) {
|
|
10386
|
+
case "user":
|
|
10387
|
+
case "assistant_text":
|
|
10388
|
+
case "thinking":
|
|
10389
|
+
case "notice": return item.text;
|
|
10390
|
+
case "tool_call": return `${item.name}(${toolInputPreview(item.input)})`;
|
|
10391
|
+
case "turn_result": return doneLine(item);
|
|
10392
|
+
case "file_delivered": return item.path;
|
|
10393
|
+
default: return "";
|
|
10394
|
+
}
|
|
10395
|
+
}
|
|
10396
|
+
function buildMarks(items, { frameParentId, bookmarks = [], recapItemIndex } = {}) {
|
|
10397
|
+
const marks = [];
|
|
10398
|
+
const subagentParents = /* @__PURE__ */ new Set();
|
|
10399
|
+
for (const item of items) {
|
|
10400
|
+
const parent = parentOf(item);
|
|
10401
|
+
if (parent !== void 0) subagentParents.add(parent);
|
|
10402
|
+
}
|
|
10403
|
+
let segment = {};
|
|
10404
|
+
const closeSegment = () => {
|
|
10405
|
+
const anchor = segment.response ?? segment.turn;
|
|
10406
|
+
if (anchor !== void 0) marks.push({
|
|
10407
|
+
kind: segment.failed ? "turnFailed" : "turn",
|
|
10408
|
+
itemIndex: anchor,
|
|
10409
|
+
turnIndex: segment.turn
|
|
10410
|
+
});
|
|
10411
|
+
segment = {};
|
|
10412
|
+
};
|
|
10413
|
+
items.forEach((item, index) => {
|
|
10414
|
+
if (item.kind === "tool_call" && subagentParents.has(item.id)) marks.push({
|
|
10415
|
+
kind: "subagent",
|
|
10416
|
+
itemIndex: index
|
|
10417
|
+
});
|
|
10418
|
+
if (item.kind === "user" && parentOf(item) === frameParentId) {
|
|
10419
|
+
closeSegment();
|
|
10420
|
+
marks.push({
|
|
10421
|
+
kind: "user",
|
|
10422
|
+
itemIndex: index
|
|
10423
|
+
});
|
|
10424
|
+
} else if (item.kind === "turn_result") {
|
|
10425
|
+
segment.turn = index;
|
|
10426
|
+
segment.failed = item.isError;
|
|
10427
|
+
closeSegment();
|
|
10428
|
+
} else if (item.kind === "notice" && item.level === "error") marks.push({
|
|
10429
|
+
kind: "error",
|
|
10430
|
+
itemIndex: index
|
|
10431
|
+
});
|
|
10432
|
+
else if (item.kind === "tool_call" && parentOf(item) === frameParentId && (item.status === "failed" || item.result?.isError === true)) marks.push({
|
|
10433
|
+
kind: "toolFailed",
|
|
10434
|
+
itemIndex: index
|
|
10435
|
+
});
|
|
10436
|
+
else if (item.kind === "assistant_text" && parentOf(item) === frameParentId) {
|
|
10437
|
+
if (frameParentId !== void 0) {
|
|
10438
|
+
marks.push({
|
|
10439
|
+
kind: "turn",
|
|
10440
|
+
itemIndex: index
|
|
10441
|
+
});
|
|
10442
|
+
return;
|
|
10443
|
+
}
|
|
10444
|
+
segment.response = index;
|
|
10445
|
+
}
|
|
10446
|
+
});
|
|
10447
|
+
closeSegment();
|
|
10448
|
+
for (const index of bookmarks) if (index >= 0 && index < items.length) marks.push({
|
|
10449
|
+
kind: "bookmark",
|
|
10450
|
+
itemIndex: index
|
|
10451
|
+
});
|
|
10452
|
+
if (recapItemIndex !== void 0) marks.push({
|
|
10453
|
+
kind: "recap",
|
|
10454
|
+
itemIndex: recapItemIndex
|
|
10455
|
+
});
|
|
10456
|
+
return marks;
|
|
10457
|
+
}
|
|
10458
|
+
/**
|
|
10459
|
+
* Place marks proportionally — y = `itemIndex / itemCount` of the rail, every
|
|
10460
|
+
* mark one item's share tall (floored at {@link MIN_MARK}) — then merge
|
|
10461
|
+
* adjacent ones per lane, the loudest colour winning. The same merge rule as
|
|
10462
|
+
* the terminal rail; only the position source differs.
|
|
10463
|
+
*/
|
|
10464
|
+
function clusterMarks(marks, railH, itemCount) {
|
|
10465
|
+
const count = Math.max(1, itemCount);
|
|
10466
|
+
const h = Math.max(2, Math.round(railH / count));
|
|
10467
|
+
const lanes = /* @__PURE__ */ new Map();
|
|
10468
|
+
for (const mark of marks) {
|
|
10469
|
+
const y = Math.min(Math.max(0, railH - h), Math.round(mark.itemIndex / count * railH));
|
|
10470
|
+
const lane = LANE[mark.kind];
|
|
10471
|
+
const list = lanes.get(lane) ?? [];
|
|
10472
|
+
list.push({
|
|
10473
|
+
mark,
|
|
10474
|
+
y
|
|
10475
|
+
});
|
|
10476
|
+
lanes.set(lane, list);
|
|
10477
|
+
}
|
|
10478
|
+
const clusters = [];
|
|
10479
|
+
for (const [lane, list] of lanes) {
|
|
10480
|
+
list.sort((a, b) => a.y - b.y);
|
|
10481
|
+
let current = null;
|
|
10482
|
+
for (const { mark, y } of list) if (current && y <= current.y + current.h + 1) {
|
|
10483
|
+
current.h = Math.max(current.h, y + h - current.y);
|
|
10484
|
+
if (LOUDNESS[mark.kind] > LOUDNESS[current.kind]) current.kind = mark.kind;
|
|
10485
|
+
current.marks.push({
|
|
10486
|
+
mark,
|
|
10487
|
+
y
|
|
10488
|
+
});
|
|
10489
|
+
} else {
|
|
10490
|
+
current = {
|
|
10491
|
+
lane,
|
|
10492
|
+
kind: mark.kind,
|
|
10493
|
+
y,
|
|
10494
|
+
h,
|
|
10495
|
+
marks: [{
|
|
10496
|
+
mark,
|
|
10497
|
+
y
|
|
10498
|
+
}]
|
|
10499
|
+
};
|
|
10500
|
+
clusters.push(current);
|
|
10501
|
+
}
|
|
10502
|
+
}
|
|
10503
|
+
return clusters;
|
|
10504
|
+
}
|
|
10505
|
+
/** The approval is not an item — the prompt renders below the transcript — so
|
|
10506
|
+
* its mark pins at the rail's foot, where the prompt is. Built by hand (no
|
|
10507
|
+
* item to derive a position from), hence `marks: []`. */
|
|
10508
|
+
function approvalCluster(railH) {
|
|
10509
|
+
return {
|
|
10510
|
+
lane: LANE.approval,
|
|
10511
|
+
kind: "approval",
|
|
10512
|
+
y: Math.max(0, railH - 2),
|
|
10513
|
+
h: 2,
|
|
10514
|
+
marks: []
|
|
10515
|
+
};
|
|
10516
|
+
}
|
|
10517
|
+
//#endregion
|
|
10518
|
+
//#region src/components/agent/Scrubber.tsx
|
|
10519
|
+
function peekContent(cluster, first, items, pendingApprovals) {
|
|
10520
|
+
const more = cluster.marks.length > 1 ? ` · ${cluster.marks.length} marks` : "";
|
|
10521
|
+
let body = null;
|
|
10522
|
+
if (cluster.kind === "approval") {
|
|
10523
|
+
const request = pendingApprovals[0];
|
|
10524
|
+
body = request ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", { children: request.title ?? "Permission required" }), /* @__PURE__ */ jsx("div", {
|
|
10525
|
+
className: "wd-scrub-ex",
|
|
10526
|
+
"data-tone": "muted",
|
|
10527
|
+
children: `${request.displayName ?? request.toolName}(${toolInputPreview(request.input)})`
|
|
10528
|
+
})] }) : null;
|
|
10529
|
+
} else if (first && first.kind !== "recap") {
|
|
10530
|
+
const item = items[first.itemIndex];
|
|
10531
|
+
if (first.kind === "turn" || first.kind === "turnFailed") {
|
|
10532
|
+
const turn = first.turnIndex === void 0 ? void 0 : items[first.turnIndex];
|
|
10533
|
+
body = /* @__PURE__ */ jsxs(Fragment$1, { children: [item?.kind === "assistant_text" ? /* @__PURE__ */ jsx("div", {
|
|
10534
|
+
className: "wd-scrub-ex",
|
|
10535
|
+
children: item.text
|
|
10536
|
+
}) : null, turn?.kind === "turn_result" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
10537
|
+
"data-tone": turn.isError ? "danger" : "muted",
|
|
10538
|
+
children: doneLine(turn)
|
|
10539
|
+
}), turn.errors?.map((message, index) => /* @__PURE__ */ jsx("div", {
|
|
10540
|
+
"data-tone": "danger",
|
|
10541
|
+
children: message
|
|
10542
|
+
}, index))] }) : null] });
|
|
10543
|
+
} else if (item) {
|
|
10544
|
+
const failure = first.kind === "toolFailed" && item.kind === "tool_call" ? item.result?.text.split("\n").find((line) => line.trim() !== "") : void 0;
|
|
10545
|
+
body = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
10546
|
+
className: "wd-scrub-ex",
|
|
10547
|
+
"data-tone": first.kind === "error" || first.kind === "toolFailed" ? "danger" : void 0,
|
|
10548
|
+
children: [first.kind === "user" ? /* @__PURE__ */ jsx("span", {
|
|
10549
|
+
"data-tone": "muted",
|
|
10550
|
+
children: "❯ "
|
|
10551
|
+
}) : null, excerpt(item)]
|
|
10552
|
+
}), failure ? /* @__PURE__ */ jsx("div", {
|
|
10553
|
+
className: "wd-scrub-ex",
|
|
10554
|
+
"data-tone": "danger",
|
|
10555
|
+
children: failure
|
|
10556
|
+
}) : null] });
|
|
10557
|
+
}
|
|
10558
|
+
}
|
|
10559
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
10560
|
+
"data-tone": "muted",
|
|
10561
|
+
children: [KIND_NAME[first?.kind ?? cluster.kind], more]
|
|
10562
|
+
}), body] });
|
|
10563
|
+
}
|
|
10564
|
+
function Scrubber({ items, pendingApprovals, recapItemIndex, bookmarks, frameParentId, interactive = false, onJumpToItem, className }) {
|
|
10565
|
+
const railRef = useRef(null);
|
|
10566
|
+
const peekRef = useRef(null);
|
|
10567
|
+
const [railH, setRailH] = useState(0);
|
|
10568
|
+
const [peek, setPeek] = useState(null);
|
|
10569
|
+
useEffect(() => {
|
|
10570
|
+
const element = railRef.current;
|
|
10571
|
+
if (!element) return;
|
|
10572
|
+
const observer = new ResizeObserver(() => setRailH(element.clientHeight));
|
|
10573
|
+
observer.observe(element);
|
|
10574
|
+
setRailH(element.clientHeight);
|
|
10575
|
+
return () => observer.disconnect();
|
|
10576
|
+
}, []);
|
|
10577
|
+
useEffect(() => {
|
|
10578
|
+
setPeek(null);
|
|
10579
|
+
}, [items]);
|
|
10580
|
+
useLayoutEffect(() => {
|
|
10581
|
+
const element = peekRef.current;
|
|
10582
|
+
if (!element || !peek) return;
|
|
10583
|
+
const height = element.offsetHeight;
|
|
10584
|
+
const railHeight = railRef.current?.clientHeight ?? 0;
|
|
10585
|
+
element.style.top = `${Math.max(4, Math.min(railHeight - height - 4, peek.y - height / 2))}px`;
|
|
10586
|
+
}, [peek]);
|
|
10587
|
+
const clusters = useMemo(() => {
|
|
10588
|
+
if (railH <= 0) return [];
|
|
10589
|
+
const built = clusterMarks(buildMarks(items, {
|
|
10590
|
+
frameParentId,
|
|
10591
|
+
bookmarks,
|
|
10592
|
+
recapItemIndex
|
|
10593
|
+
}), railH, items.length);
|
|
10594
|
+
if (pendingApprovals.length > 0) built.push(approvalCluster(railH));
|
|
10595
|
+
return built;
|
|
10596
|
+
}, [
|
|
10597
|
+
items,
|
|
10598
|
+
frameParentId,
|
|
10599
|
+
bookmarks,
|
|
10600
|
+
recapItemIndex,
|
|
10601
|
+
pendingApprovals,
|
|
10602
|
+
railH
|
|
10603
|
+
]);
|
|
10604
|
+
/** A pointer's y in rail space. */
|
|
10605
|
+
const railY = (clientY) => clientY - (railRef.current?.getBoundingClientRect().top ?? 0);
|
|
10606
|
+
const activate = (cluster, clientY) => {
|
|
10607
|
+
if (cluster.kind === "approval" && cluster.marks.length === 0) {
|
|
10608
|
+
if (items.length > 0) onJumpToItem?.(items.length - 1);
|
|
10609
|
+
return;
|
|
10610
|
+
}
|
|
10611
|
+
const mark = nearestMember(cluster, railY(clientY));
|
|
10612
|
+
if (mark) onJumpToItem?.(mark.itemIndex);
|
|
10613
|
+
};
|
|
10614
|
+
const showPeek = (cluster, clientY) => {
|
|
10615
|
+
const y = railY(clientY);
|
|
10616
|
+
const mark = nearestMember(cluster, y);
|
|
10617
|
+
setPeek((previous) => previous && previous.cluster === cluster && previous.mark === mark ? previous : {
|
|
10618
|
+
cluster,
|
|
10619
|
+
mark,
|
|
10620
|
+
y: Math.min(Math.max(y, cluster.y), cluster.y + cluster.h)
|
|
10621
|
+
});
|
|
10622
|
+
};
|
|
10623
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10624
|
+
ref: railRef,
|
|
10625
|
+
className: cn("wd-scrubber", className),
|
|
10626
|
+
"data-interactive": interactive || void 0,
|
|
10627
|
+
"aria-hidden": true,
|
|
10628
|
+
children: [clusters.map((cluster, index) => /* @__PURE__ */ jsx("div", {
|
|
10629
|
+
className: "wd-scrub-mark",
|
|
10630
|
+
"data-lane": cluster.lane,
|
|
10631
|
+
"data-kind": cluster.kind,
|
|
10632
|
+
style: {
|
|
10633
|
+
top: cluster.y,
|
|
10634
|
+
height: cluster.h
|
|
10635
|
+
},
|
|
10636
|
+
...interactive ? {
|
|
10637
|
+
onClick: (event) => activate(cluster, event.clientY),
|
|
10638
|
+
onPointerEnter: (event) => showPeek(cluster, event.clientY),
|
|
10639
|
+
onPointerMove: (event) => showPeek(cluster, event.clientY),
|
|
10640
|
+
onPointerLeave: () => setPeek(null)
|
|
10641
|
+
} : null
|
|
10642
|
+
}, index)), peek ? /* @__PURE__ */ jsx("div", {
|
|
10643
|
+
ref: peekRef,
|
|
10644
|
+
className: "wd-scrub-peek",
|
|
10645
|
+
style: { top: peek.y },
|
|
10646
|
+
children: peekContent(peek.cluster, peek.mark, items, pendingApprovals)
|
|
10647
|
+
}) : null]
|
|
10648
|
+
});
|
|
10649
|
+
}
|
|
10650
|
+
//#endregion
|
|
10298
10651
|
//#region src/components/agent/transcript-rows.ts
|
|
10299
10652
|
/** The item a row is spaced *as*. A run stands for the calls it folded, and a
|
|
10300
10653
|
* task block for the `Task` call it absorbed into — all tool calls, so a run,
|
|
@@ -10962,36 +11315,69 @@ function nestedClass(item, frameParentId) {
|
|
|
10962
11315
|
const parent = "parentToolUseId" in item ? item.parentToolUseId : void 0;
|
|
10963
11316
|
return parent != null && parent !== frameParentId ? "border-l-2 border-border pl-3" : void 0;
|
|
10964
11317
|
}
|
|
11318
|
+
/** The cards head's one line: the prompt as a plain run of text. Deliberately
|
|
11319
|
+
* not the Message component — a proportional card clipped by height is a
|
|
11320
|
+
* sliced bubble, and un-styling one from CSS is a fight (see theme.css's
|
|
11321
|
+
* sticky-prompt block for the other half of this decision). Prompt syntax
|
|
11322
|
+
* stays literal — a 28px bar is a reminder of what was asked, not a rendering
|
|
11323
|
+
* surface — and newlines collapse under the bar's `nowrap`. Attachment-only
|
|
11324
|
+
* prompts fall back to the attachments' names so the bar is never blank. */
|
|
11325
|
+
function promptHeadText(item) {
|
|
11326
|
+
return item.text || (item.attachments ?? []).map((attachment) => attachment.name).join(", ");
|
|
11327
|
+
}
|
|
10965
11328
|
/**
|
|
10966
11329
|
* A prompt row's sticky lane — the strip spanning its turn, leading with the
|
|
10967
11330
|
* one-line pinned **head** (see the pinned-prompt comment in
|
|
10968
11331
|
* {@link TranscriptRows}).
|
|
10969
11332
|
*
|
|
11333
|
+
* The head's content is the variant's: the terminal passes the row again
|
|
11334
|
+
* (clipping it to one line is exact under a monospace grid), cards passes the
|
|
11335
|
+
* prompt as plain text for theme.css to draw as a compact bar. Only the
|
|
11336
|
+
* terminal head carries the row's gap class — its stuck geometry parks that
|
|
11337
|
+
* padding above the viewport edge so the visible line docks at zero
|
|
11338
|
+
* (terminal.css); the cards bar never aligns with the row in flow, so the gap
|
|
11339
|
+
* stays off it entirely and the 1st prompt and the Nth share one geometry.
|
|
11340
|
+
*
|
|
10970
11341
|
* The head starts `visibility: hidden` and shows only while actually stuck —
|
|
10971
11342
|
* an overlay that is visible in flow would sit on the real row's first line
|
|
10972
11343
|
* and swallow its selection highlight, which reads as "the first line cannot
|
|
10973
11344
|
* be selected". CSS cannot ask "am I stuck?", so a 1px sentinel at the head's
|
|
10974
|
-
* engage threshold (the line's own y)
|
|
10975
|
-
*
|
|
10976
|
-
*
|
|
10977
|
-
|
|
10978
|
-
|
|
11345
|
+
* engage threshold (the line's own y) answers it: sentinel above the
|
|
11346
|
+
* scrollport top → stuck, read by a passive scroll listener. It was an
|
|
11347
|
+
* IntersectionObserver once, for the "no per-scroll work" purity — and that
|
|
11348
|
+
* was a real bug: IO is edge-triggered, and an *instant* jump (the open-at-
|
|
11349
|
+
* bottom pin, `jumpToRow`, a reveal) teleports the sentinel from below the
|
|
11350
|
+
* viewport to above it between two observations — ratio 0 → 0, no threshold
|
|
11351
|
+
* crossed, `isIntersecting` unchanged — so no entry is ever queued and the
|
|
11352
|
+
* flag strands, in whichever direction the jump left it (observed: a session
|
|
11353
|
+
* opened at the bottom, its prompt bar missing; the stale-true twin paints a
|
|
11354
|
+
* bar over the real bubble). The flag needs level-triggered truth. The cost
|
|
11355
|
+
* is two rect reads per scroll event per mounted lane — layout is clean
|
|
11356
|
+
* during scrolling, and the pin itself is still the compositor's; only the
|
|
11357
|
+
* bar's visibility rides the listener.
|
|
11358
|
+
*/
|
|
11359
|
+
function StickyPromptLane({ top, height, gapClass, gapPx, terminal, scrollRoot, index, measureRef, head, content }) {
|
|
10979
11360
|
const headRef = useRef(null);
|
|
10980
11361
|
const sentinelRef = useRef(null);
|
|
10981
11362
|
useEffect(() => {
|
|
10982
|
-
const
|
|
11363
|
+
const headElement = headRef.current;
|
|
10983
11364
|
const sentinel = sentinelRef.current;
|
|
10984
|
-
if (!
|
|
10985
|
-
const
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
return () =>
|
|
10992
|
-
}, [
|
|
11365
|
+
if (!headElement || !sentinel || !scrollRoot) return;
|
|
11366
|
+
const evaluate = () => {
|
|
11367
|
+
const stuck = sentinel.getBoundingClientRect().top < scrollRoot.getBoundingClientRect().top;
|
|
11368
|
+
headElement.toggleAttribute("data-stuck", stuck);
|
|
11369
|
+
};
|
|
11370
|
+
evaluate();
|
|
11371
|
+
scrollRoot.addEventListener("scroll", evaluate, { passive: true });
|
|
11372
|
+
return () => scrollRoot.removeEventListener("scroll", evaluate);
|
|
11373
|
+
}, [
|
|
11374
|
+
scrollRoot,
|
|
11375
|
+
top,
|
|
11376
|
+
height,
|
|
11377
|
+
gapClass
|
|
11378
|
+
]);
|
|
10993
11379
|
return /* @__PURE__ */ jsxs("div", {
|
|
10994
|
-
"data-sticky-lane": "",
|
|
11380
|
+
"data-sticky-lane": terminal ? "terminal" : "cards",
|
|
10995
11381
|
className: "absolute inset-x-0",
|
|
10996
11382
|
style: {
|
|
10997
11383
|
top,
|
|
@@ -11004,8 +11390,8 @@ function StickyPromptLane({ top, height, gapClass, scrollRoot, index, measureRef
|
|
|
11004
11390
|
children: /* @__PURE__ */ jsx("div", {
|
|
11005
11391
|
ref: headRef,
|
|
11006
11392
|
"data-sticky-head": "",
|
|
11007
|
-
className: gapClass || void 0,
|
|
11008
|
-
children:
|
|
11393
|
+
className: terminal && gapClass || void 0,
|
|
11394
|
+
children: head
|
|
11009
11395
|
})
|
|
11010
11396
|
}),
|
|
11011
11397
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -11013,7 +11399,7 @@ function StickyPromptLane({ top, height, gapClass, scrollRoot, index, measureRef
|
|
|
11013
11399
|
"aria-hidden": true,
|
|
11014
11400
|
className: "absolute left-0 w-px",
|
|
11015
11401
|
style: {
|
|
11016
|
-
top: gapClass ? "var(--term-line)" : 0,
|
|
11402
|
+
top: gapClass ? terminal ? "var(--term-line)" : gapPx : 0,
|
|
11017
11403
|
height: 1
|
|
11018
11404
|
}
|
|
11019
11405
|
}),
|
|
@@ -11069,7 +11455,7 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
11069
11455
|
promptRows: []
|
|
11070
11456
|
});
|
|
11071
11457
|
pinRef.current = {
|
|
11072
|
-
enabled:
|
|
11458
|
+
enabled: stickyPrompt,
|
|
11073
11459
|
promptRows
|
|
11074
11460
|
};
|
|
11075
11461
|
useEffect(() => {
|
|
@@ -11265,16 +11651,19 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
11265
11651
|
onOpenSubagent
|
|
11266
11652
|
})
|
|
11267
11653
|
});
|
|
11268
|
-
if (
|
|
11654
|
+
if (stickyPrompt && "item" in row && row.item.kind === "user" && parentOf(row.item) === void 0) {
|
|
11269
11655
|
const next = promptRows.find((index) => index > virtualRow.index);
|
|
11270
11656
|
const laneEnd = next === void 0 ? virtualizer.getTotalSize() : measurements[next]?.start ?? virtualRow.start;
|
|
11271
11657
|
return /* @__PURE__ */ jsx(StickyPromptLane, {
|
|
11272
11658
|
top: virtualRow.start,
|
|
11273
11659
|
height: Math.max(laneEnd - virtualRow.start, 0),
|
|
11274
11660
|
gapClass,
|
|
11661
|
+
gapPx: gap.px,
|
|
11662
|
+
terminal,
|
|
11275
11663
|
scrollRoot: scrollElement,
|
|
11276
11664
|
index: virtualRow.index,
|
|
11277
11665
|
measureRef: virtualizer.measureElement,
|
|
11666
|
+
head: terminal ? content : promptHeadText(row.item),
|
|
11278
11667
|
content
|
|
11279
11668
|
}, row.key);
|
|
11280
11669
|
}
|
|
@@ -11285,7 +11674,7 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
11285
11674
|
style: { transform: `translateY(${virtualRow.start}px)` },
|
|
11286
11675
|
children: content
|
|
11287
11676
|
}, row.key);
|
|
11288
|
-
}),
|
|
11677
|
+
}), scrubber && scrollElement?.parentElement ? createPortal(terminal ? /* @__PURE__ */ jsx(TerminalScrubber, {
|
|
11289
11678
|
items,
|
|
11290
11679
|
pendingApprovals,
|
|
11291
11680
|
recapRow,
|
|
@@ -11302,10 +11691,18 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
11302
11691
|
interactive: scrubInteractive,
|
|
11303
11692
|
fontSize,
|
|
11304
11693
|
lineHeight
|
|
11694
|
+
}) : /* @__PURE__ */ jsx(Scrubber, {
|
|
11695
|
+
items,
|
|
11696
|
+
pendingApprovals,
|
|
11697
|
+
recapItemIndex: recapIndex >= 0 ? boundary : void 0,
|
|
11698
|
+
bookmarks: scrubberMarks,
|
|
11699
|
+
frameParentId,
|
|
11700
|
+
interactive: scrubInteractive,
|
|
11701
|
+
onJumpToItem: (itemIndex) => jumpToRow(rowIndexForItem(rows, itemIndex), "start")
|
|
11305
11702
|
}), scrollElement.parentElement) : null]
|
|
11306
11703
|
});
|
|
11307
11704
|
}
|
|
11308
|
-
function Transcript({ state, fileUrl, attachmentUrl, canBrowseFiles, hostImage, variant = "cards", density = "comfortable", fontSize, lineHeight, affordances, stickyPrompt = false, scrubber, scrubberMarks, replaying = false, catchUp, jumpToRecapRef, repinRef, reveal, frame, onOpenSubagent, className }) {
|
|
11705
|
+
function Transcript({ state, fileUrl, attachmentUrl, canBrowseFiles, hostImage, variant = "cards", density = "comfortable", fontSize, lineHeight, affordances, stickyPrompt = false, scrubber, scrubberMarks, replaying = false, catchUp, jumpToRecapRef, repinRef, reveal, frame, onOpenSubagent, emptyState, className }) {
|
|
11309
11706
|
const terminal = variant === "terminal";
|
|
11310
11707
|
const items = useMemo(() => frame ? subagentItems(state.items, frame.parentToolUseId) : state.items, [state.items, frame]);
|
|
11311
11708
|
const frameTask = useMemo(() => frame ? state.items.find((item) => item.kind === "tool_call" && item.id === frame.parentToolUseId) : void 0, [state.items, frame]);
|
|
@@ -11356,7 +11753,7 @@ function Transcript({ state, fileUrl, attachmentUrl, canBrowseFiles, hostImage,
|
|
|
11356
11753
|
frame ? frameTask === void 0 && !replaying ? /* @__PURE__ */ jsx("div", {
|
|
11357
11754
|
className: cn(terminal ? "term-row text-fg-4" : "p-4 text-body-sm text-fg-4"),
|
|
11358
11755
|
children: "This sub-agent's work is not in this transcript."
|
|
11359
|
-
}) : null : items.length === 0 && state.status !== "starting" ? /* @__PURE__ */ jsx(SessionEmptyState, {
|
|
11756
|
+
}) : null : items.length === 0 && state.status !== "starting" ? emptyState !== void 0 ? emptyState : /* @__PURE__ */ jsx(SessionEmptyState, {
|
|
11360
11757
|
cwd: state.cwd,
|
|
11361
11758
|
hasCommands: !!state.commands?.length,
|
|
11362
11759
|
hasSkills: !!state.skills?.some((s) => s.enabled),
|
|
@@ -11407,7 +11804,7 @@ function Transcript({ state, fileUrl, attachmentUrl, canBrowseFiles, hostImage,
|
|
|
11407
11804
|
"aria-hidden": true,
|
|
11408
11805
|
className: "wd-hold-appear visible pointer-events-none absolute inset-0 overflow-hidden",
|
|
11409
11806
|
children: /* @__PURE__ */ jsx("div", {
|
|
11410
|
-
className: cn("mx-auto w-full max-w-[var(--wd-
|
|
11807
|
+
className: cn("mx-auto w-full max-w-[var(--wd-transcript-max-width)]", !terminal && "px-4 py-4"),
|
|
11411
11808
|
children: terminal ? /* @__PURE__ */ jsx(TerminalSurface, {
|
|
11412
11809
|
fontSize,
|
|
11413
11810
|
lineHeight,
|
|
@@ -11531,42 +11928,45 @@ function UsageMeter({ window, now }) {
|
|
|
11531
11928
|
* The session's view of the plan: every window as a meter (see
|
|
11532
11929
|
* {@link UsageMeters}), the plan tier, and what this session itself has cost.
|
|
11533
11930
|
*/
|
|
11534
|
-
function UsageDialog({ rateLimits, subscriptionType, engine, totalCostUsd, updatedAt, open, onOpenChange }) {
|
|
11931
|
+
function UsageDialog({ rateLimits, subscriptionType, engine, totalCostUsd, updatedAt, open, onOpenChange, className }) {
|
|
11535
11932
|
const now = useMinuteClock(open);
|
|
11536
11933
|
return /* @__PURE__ */ jsx(Dialog$1, {
|
|
11537
11934
|
open,
|
|
11538
11935
|
onOpenChange,
|
|
11539
|
-
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
className: "
|
|
11559
|
-
children:
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11936
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
11937
|
+
className,
|
|
11938
|
+
children: [/* @__PURE__ */ jsx(DialogHeader, {
|
|
11939
|
+
title: "Usage",
|
|
11940
|
+
description: engine === "claude" ? "Claude Code" : engine,
|
|
11941
|
+
actions: subscriptionType ? /* @__PURE__ */ jsx(Badge, {
|
|
11942
|
+
variant: "accent",
|
|
11943
|
+
className: "mt-0.5 shrink-0 capitalize",
|
|
11944
|
+
children: subscriptionType
|
|
11945
|
+
}) : null
|
|
11946
|
+
}), /* @__PURE__ */ jsxs(DialogBody, { children: [
|
|
11947
|
+
rateLimits.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
11948
|
+
className: "py-6 text-center text-body-sm text-fg-4",
|
|
11949
|
+
children: engine === "claude" ? "This session reports no plan windows — API-key sessions have none, and a subscription session reports them once a turn has run." : `Plan windows are a claude.ai subscription thing; this session runs on the ${engine} engine.`
|
|
11950
|
+
}) : /* @__PURE__ */ jsx(UsageMeters, {
|
|
11951
|
+
windows: rateLimits,
|
|
11952
|
+
now
|
|
11953
|
+
}),
|
|
11954
|
+
/* @__PURE__ */ jsxs("div", {
|
|
11955
|
+
className: "mt-5 flex items-baseline justify-between gap-4 border-t border-border pt-3",
|
|
11956
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
11957
|
+
className: "text-label text-fg-3",
|
|
11958
|
+
children: "This session has cost"
|
|
11959
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
11960
|
+
className: "font-mono text-body-sm text-fg-1",
|
|
11961
|
+
children: formatCost(totalCostUsd)
|
|
11962
|
+
})]
|
|
11963
|
+
}),
|
|
11964
|
+
updatedAt ? /* @__PURE__ */ jsxs("p", {
|
|
11965
|
+
className: "mt-2 text-label text-fg-4",
|
|
11966
|
+
children: ["Updated ", formatAgoPrecise(updatedAt, now)]
|
|
11967
|
+
}) : null
|
|
11968
|
+
] })]
|
|
11969
|
+
})
|
|
11570
11970
|
});
|
|
11571
11971
|
}
|
|
11572
11972
|
//#endregion
|
|
@@ -11581,7 +11981,7 @@ function UsageDialog({ rateLimits, subscriptionType, engine, totalCostUsd, updat
|
|
|
11581
11981
|
*/
|
|
11582
11982
|
function PromptSurface({ terminal, metrics, affordances, children }) {
|
|
11583
11983
|
if (!terminal) return /* @__PURE__ */ jsx("div", {
|
|
11584
|
-
className: "mx-auto flex w-full max-w-[var(--wd-
|
|
11984
|
+
className: "mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] flex-col gap-2",
|
|
11585
11985
|
children
|
|
11586
11986
|
});
|
|
11587
11987
|
return /* @__PURE__ */ jsx(TerminalSurface, {
|
|
@@ -11620,7 +12020,7 @@ const INTERACTIVE = [
|
|
|
11620
12020
|
* the engine name — an absent capability hides the control instead of offering
|
|
11621
12021
|
* one that can only fail.
|
|
11622
12022
|
*/
|
|
11623
|
-
function SessionPanel({ client, sessionId, header, panelSurface = "internal", statusSurface = "internal", statusPlacement = "top", onOpenPanel, onVitals, transcriptVariant = "cards", transcriptDensity = "comfortable", transcriptFont = "sans", affordances, terminalMetrics, scrubber = false, scrubberMarks, reveal, openSubagent, stickyPrompt = false, controlsSurface = "internal", onControls, focusComposerOnClick = false, unseen, readOnly = false, toolHost, cacheTranscript, className }) {
|
|
12023
|
+
function SessionPanel({ client, sessionId, header, panelSurface = "internal", statusSurface = "internal", statusPlacement = "top", onOpenPanel, onVitals, transcriptVariant = "cards", transcriptDensity = "comfortable", transcriptFont = "sans", affordances, terminalMetrics, scrubber = false, scrubberMarks, reveal, openSubagent, onSubagentChange, stickyPrompt = false, controlsSurface = "internal", onControls, focusComposerOnClick = false, unseen, readOnly = false, toolHost, cacheTranscript, emptyState, className }) {
|
|
11624
12024
|
const external = panelSurface === "external";
|
|
11625
12025
|
const statusExternal = statusSurface === "external";
|
|
11626
12026
|
const controlsInStatus = controlsSurface === "status" && !statusExternal;
|
|
@@ -11657,25 +12057,25 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
|
|
|
11657
12057
|
setSubagentId(void 0);
|
|
11658
12058
|
setReturnReveal(void 0);
|
|
11659
12059
|
}, [sessionId]);
|
|
12060
|
+
const leaveSubagent = useCallback(() => {
|
|
12061
|
+
setSubagentId((current) => {
|
|
12062
|
+
if (current !== void 0) setReturnReveal({
|
|
12063
|
+
toolUseId: current,
|
|
12064
|
+
nonce: Date.now()
|
|
12065
|
+
});
|
|
12066
|
+
});
|
|
12067
|
+
}, []);
|
|
11660
12068
|
const openSubagentNonce = openSubagent?.nonce;
|
|
11661
12069
|
const openSubagentId = openSubagent?.toolUseId;
|
|
11662
12070
|
useEffect(() => {
|
|
11663
|
-
if (openSubagentId === void 0)
|
|
11664
|
-
setSubagentId(openSubagentId);
|
|
12071
|
+
if (openSubagentId === void 0) leaveSubagent();
|
|
12072
|
+
else setSubagentId(openSubagentId);
|
|
11665
12073
|
}, [openSubagentNonce]);
|
|
11666
12074
|
const revealNonce = reveal?.nonce;
|
|
11667
12075
|
useEffect(() => {
|
|
11668
12076
|
if (revealNonce === void 0) return;
|
|
11669
12077
|
setSubagentId(void 0);
|
|
11670
12078
|
}, [revealNonce]);
|
|
11671
|
-
const leaveSubagent = useCallback(() => {
|
|
11672
|
-
setSubagentId((current) => {
|
|
11673
|
-
if (current !== void 0) setReturnReveal({
|
|
11674
|
-
toolUseId: current,
|
|
11675
|
-
nonce: Date.now()
|
|
11676
|
-
});
|
|
11677
|
-
});
|
|
11678
|
-
}, []);
|
|
11679
12079
|
useEffect(() => {
|
|
11680
12080
|
if (subagentId === void 0) return;
|
|
11681
12081
|
const onKey = (event) => {
|
|
@@ -11685,6 +12085,14 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
|
|
|
11685
12085
|
window.addEventListener("keydown", onKey);
|
|
11686
12086
|
return () => window.removeEventListener("keydown", onKey);
|
|
11687
12087
|
}, [subagentId, leaveSubagent]);
|
|
12088
|
+
const onSubagentChangeRef = useRef(onSubagentChange);
|
|
12089
|
+
onSubagentChangeRef.current = onSubagentChange;
|
|
12090
|
+
const reportedSubagentId = useRef(void 0);
|
|
12091
|
+
useEffect(() => {
|
|
12092
|
+
if (reportedSubagentId.current === subagentId) return;
|
|
12093
|
+
reportedSubagentId.current = subagentId;
|
|
12094
|
+
onSubagentChangeRef.current?.(subagentId);
|
|
12095
|
+
}, [subagentId]);
|
|
11688
12096
|
const [caughtUp, setCaughtUp] = useState(false);
|
|
11689
12097
|
useEffect(() => {
|
|
11690
12098
|
setCaughtUp(false);
|
|
@@ -11743,7 +12151,8 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
|
|
|
11743
12151
|
cwd: state.cwd,
|
|
11744
12152
|
contextUsage: state.contextUsage,
|
|
11745
12153
|
rateLimits,
|
|
11746
|
-
itemCount: state.items.length
|
|
12154
|
+
itemCount: state.items.length,
|
|
12155
|
+
totalCostUsd: state.totalCostUsd
|
|
11747
12156
|
});
|
|
11748
12157
|
}, [
|
|
11749
12158
|
state.status,
|
|
@@ -11757,7 +12166,8 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
|
|
|
11757
12166
|
state.cwd,
|
|
11758
12167
|
state.contextUsage,
|
|
11759
12168
|
rateLimits,
|
|
11760
|
-
state.items.length
|
|
12169
|
+
state.items.length,
|
|
12170
|
+
state.totalCostUsd
|
|
11761
12171
|
]);
|
|
11762
12172
|
const onControlsRef = useRef(onControls);
|
|
11763
12173
|
onControlsRef.current = onControls;
|
|
@@ -11946,6 +12356,7 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
|
|
|
11946
12356
|
reveal: returnReveal ?? reveal,
|
|
11947
12357
|
frame: subagentId === void 0 ? void 0 : { parentToolUseId: subagentId },
|
|
11948
12358
|
onOpenSubagent: setSubagentId,
|
|
12359
|
+
emptyState,
|
|
11949
12360
|
jumpToRecapRef: jumpToRecap,
|
|
11950
12361
|
repinRef: repinTranscript
|
|
11951
12362
|
}, subagentId ?? "session"),
|
|
@@ -11953,7 +12364,7 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
|
|
|
11953
12364
|
className: "px-3 pb-1",
|
|
11954
12365
|
children: /* @__PURE__ */ jsxs("div", {
|
|
11955
12366
|
"data-slot": "catch-up",
|
|
11956
|
-
className: "mx-auto flex w-full max-w-[var(--wd-
|
|
12367
|
+
className: "mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] items-center gap-2 text-label text-fg-3",
|
|
11957
12368
|
children: [
|
|
11958
12369
|
/* @__PURE__ */ jsx("span", {
|
|
11959
12370
|
"aria-hidden": true,
|
|
@@ -12151,7 +12562,7 @@ function Notice({ level, onDismiss, children }) {
|
|
|
12151
12562
|
className: "px-3 pt-2",
|
|
12152
12563
|
children: /* @__PURE__ */ jsxs("div", {
|
|
12153
12564
|
role: "alert",
|
|
12154
|
-
className: cn("mx-auto flex w-full max-w-[var(--wd-
|
|
12565
|
+
className: cn("mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] items-start gap-2 rounded-md border px-3 py-2 text-body-sm", level === "error" ? "border-danger/40 bg-danger-bg text-danger" : "border-warning/40 bg-warning-bg text-warning"),
|
|
12155
12566
|
children: [
|
|
12156
12567
|
/* @__PURE__ */ jsx(TriangleAlert, { className: "mt-0.5 size-3.5 shrink-0" }),
|
|
12157
12568
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -12170,6 +12581,6 @@ function Notice({ level, onDismiss, children }) {
|
|
|
12170
12581
|
});
|
|
12171
12582
|
}
|
|
12172
12583
|
//#endregion
|
|
12173
|
-
export { toolIcon as $, PERMISSION_MODES as A, MenuSeparator as At, skillPrompt as B,
|
|
12584
|
+
export { toolIcon as $, PERMISSION_MODES as A, MenuSeparator as At, skillPrompt as B, Badge as Bt, TerminalQuestionPrompt as C, DialogContent as Ct, parseUserQuestions as D, Menu$1 as Dt, QuestionPrompt as E, DialogTrigger as Et, SkillsDialog as F, SelectItemText as Ft, Band as G, TerminalMarkdown as H, Button as Ht, McpDialog as I, SelectTrigger as It, Row as J, Blank as K, HostFilesDialog as L, SelectValue as Lt, permissionModeChoices as M, Select$1 as Mt, permissionModeMeta as N, SelectContent as Nt, SubagentStrip as O, MenuContent as Ot, ModelSelect as P, SelectItem as Pt, isMutatingTool as Q, ContextDialog as R, PortalScope as Rt, SessionInfoDialog as S, DialogClose as St, QUESTION_BEHAVIORS as T, DialogRow as Tt, TerminalDiff as U, buttonVariants as Ut, TerminalSurface as V, badgeVariants as Vt, previewPatch as W, cn as Wt, WithActions as X, CopyAction as Y, useAffordances as Z, Conversation as _, Tip as _t, Transcript as a, hashtagTrigger as at, StatusBar as b, Dialog$1 as bt, ToolCallCard as c, PromptArea as ct, Response as d, ProgressRing as dt, TranscriptDensityProvider as et, PromptTokenText as f, Splitter as ft, FileCard as g, copyText as gt, Loader as h, CopyButton as ht, useMinuteClock as i, commandTrigger as it, PermissionModeSelect as j, MenuTrigger as jt, PermissionPrompt as k, MenuItem as kt, SessionEmptyState as l, plainTextToSegments as lt, MessageContent as m, Spinner as mt, UsageDialog as n, useTranscriptDensity as nt, TerminalItemView as o, mentionTrigger as ot, Message as p, CodeBlock as pt, Ink as q, UsageMeters as r, useTranscriptVariant as rt, TerminalTranscript as s, usePromptAreaState as st, SessionPanel as t, TranscriptVariantProvider as tt, Reasoning as u, segmentsToPlainText as ut, ConversationContent as v, TooltipContent as vt, TerminalPermissionPrompt as w, DialogHeader as wt, STATUS_META as x, DialogBody as xt, ConversationScrollButton as y, TooltipProvider as yt, Composer as z, Input as zt };
|
|
12174
12585
|
|
|
12175
|
-
//# sourceMappingURL=SessionPanel-
|
|
12586
|
+
//# sourceMappingURL=SessionPanel-DgwjH4Ve.mjs.map
|