@workerdeck/ui 0.16.0 → 0.18.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 +7 -0
- package/build/{SessionPanel-B9CHoq8x.d.mts → SessionPanel-CnU_IJ3-.d.mts} +36 -9
- package/build/{SessionPanel-DII9MmQ8.mjs → SessionPanel-DPx8Iz8a.mjs} +1432 -384
- package/build/SessionPanel-DPx8Iz8a.mjs.map +1 -0
- package/build/{format-DfI_je9S.d.mts → format-ljc3lKpA.d.mts} +1 -1
- package/build/format.d.mts +16 -5
- package/build/format.mjs +2 -3
- package/build/index.d.mts +288 -8
- package/build/index.mjs +620 -165
- package/build/index.mjs.map +1 -1
- package/build/{format-DqR56Y8l.mjs → status-BE-zg88x.mjs} +154 -2
- package/build/status-BE-zg88x.mjs.map +1 -0
- package/build/workspace.d.mts +4 -1
- package/build/workspace.mjs +4 -3
- package/build/workspace.mjs.map +1 -1
- package/package.json +8 -6
- package/src/components/agent/ContextRing.tsx +41 -0
- package/src/components/agent/EngineIcon.tsx +40 -0
- package/src/components/agent/ProjectIcon.tsx +119 -0
- package/src/components/agent/SessionBrowser.tsx +191 -17
- package/src/components/agent/SessionPanel.tsx +177 -2
- package/src/components/agent/SessionSteps.tsx +233 -0
- package/src/components/agent/SessionWorkspace.tsx +4 -0
- package/src/components/agent/StatusBar.tsx +4 -2
- package/src/components/agent/SubagentStrip.tsx +134 -0
- package/src/components/agent/ToolCallCard.tsx +72 -5
- package/src/components/agent/Transcript.tsx +212 -23
- package/src/components/agent/tool-result-fetch.tsx +36 -0
- package/src/components/agent/tool-result-image.tsx +209 -0
- package/src/components/agent/transcript-rows.ts +122 -23
- package/src/components/terminal/TerminalTranscript.tsx +154 -2
- package/src/components/terminal/affordances.tsx +34 -0
- package/src/components/terminal/blocks.ts +260 -0
- package/src/components/terminal/height.ts +73 -6
- package/src/components/terminal/image-box.ts +53 -0
- package/src/components/terminal/items.tsx +116 -79
- package/src/components/terminal/result-preview.ts +20 -6
- package/src/components/terminal/scrubber.tsx +172 -26
- package/src/components/terminal/tool-run.ts +177 -0
- package/src/index.ts +20 -1
- package/src/lib/status.ts +16 -3
- package/src/styles/terminal.css +85 -5
- package/src/styles/theme.css +42 -0
- package/build/SessionPanel-DII9MmQ8.mjs.map +0 -1
- package/build/format-DqR56Y8l.mjs.map +0 -1
- package/build/status-Ydzi7n6j.mjs +0 -143
- package/build/status-Ydzi7n6j.mjs.map +0 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as formatRelativeTime, b as rateLimitWindowSeconds, d as formatBytes, f as formatCost, g as formatRateLimitWindowLong, m as formatDuration, p as formatCountdown, r as meterColorClass, u as formatAgoPrecise, v as formatTokens, x as toolInputPreview } from "./status-BE-zg88x.mjs";
|
|
2
2
|
import { Fragment, createContext, forwardRef, memo, useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import { clsx } from "clsx";
|
|
5
5
|
import { extendTailwindMerge } from "tailwind-merge";
|
|
6
6
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { Select } from "@base-ui/react/select";
|
|
8
|
-
import { ArrowDown, ArrowDownCircle, ArrowUp, AtSign, Brain, ChartPie, Check, CheckSquare, ChevronDown, ChevronLeft, ChevronRight, ChevronsUpDown, ClipboardList, Clock, Code, Copy, Download, File, FileDiff, FileText, Folder, FolderSearch, FolderTree, Gauge, Globe, Hand, Image, Info, Link2, LoaderCircle, MessageCircleQuestion, MessageSquareText, MoreHorizontal, Paperclip, PencilLine, Plug, Power, PowerOff, Puzzle, RefreshCw, RotateCcw, RotateCw, Search, ShieldCheck, SlashSquare, Sparkles, Square, SquarePen, Terminal, TriangleAlert, UsersRound, WifiOff, Wrench, X, Zap } from "lucide-react";
|
|
8
|
+
import { ArrowDown, ArrowDownCircle, ArrowLeft, ArrowUp, AtSign, Brain, ChartPie, Check, CheckSquare, ChevronDown, ChevronLeft, ChevronRight, ChevronsUpDown, ClipboardList, Clock, Code, Copy, Download, File, FileDiff, FileText, Folder, FolderSearch, FolderTree, Gauge, Globe, Hand, Image, Info, Link2, LoaderCircle, MessageCircleQuestion, MessageSquareText, MoreHorizontal, Paperclip, PencilLine, Plug, Power, PowerOff, Puzzle, RefreshCw, RotateCcw, RotateCw, Search, ShieldCheck, SlashSquare, Sparkles, Square, SquarePen, Terminal, TriangleAlert, UsersRound, WifiOff, Wrench, X, Zap } from "lucide-react";
|
|
9
9
|
import { Menu } from "@base-ui/react/menu";
|
|
10
10
|
import { Dialog } from "@base-ui/react/dialog";
|
|
11
11
|
import { Tooltip } from "@base-ui/react/tooltip";
|
|
12
|
-
import { PROTOCOL_VERSION, mergeUsage, orderUsageWindows, usageInfos } from "@workerdeck/protocol";
|
|
12
|
+
import { PROTOCOL_VERSION, mergeUsage, orderUsageWindows, subagentLabel, usageInfos } from "@workerdeck/protocol";
|
|
13
13
|
import { recapLine, scanPromptTokens, summarizeSince, useAttachments, useClaudeSession, useHostFileSearch, useProfileUsage, useToolCallHost } from "@workerdeck/react";
|
|
14
14
|
import { Streamdown } from "streamdown";
|
|
15
15
|
import { createPortal } from "react-dom";
|
|
@@ -4579,6 +4579,30 @@ function WithActions({ actions, children, className }) {
|
|
|
4579
4579
|
* else is on, and reads as a button borrowed from another application. The tick
|
|
4580
4580
|
* replaces the glyph in place, so the confirmation costs no width either.
|
|
4581
4581
|
*/
|
|
4582
|
+
/**
|
|
4583
|
+
* Open a sub-agent's own surface, as a character.
|
|
4584
|
+
*
|
|
4585
|
+
* A row **action** and not the row's press, because the press already means
|
|
4586
|
+
* expand/collapse and that is the cheaper, more common intent — a reader
|
|
4587
|
+
* glancing at what an agent did wants the four rows inline, not a new screen.
|
|
4588
|
+
* Taking over the panel is the deliberate move, so it gets the deliberate
|
|
4589
|
+
* target. Same zero-layout contract as `CopyAction`: it lives in the hover
|
|
4590
|
+
* actions overlay, so a collapsed Task row is exactly as tall with it as
|
|
4591
|
+
* without, which is what keeps the height book honest.
|
|
4592
|
+
*/
|
|
4593
|
+
function OpenSubagentAction({ onOpen, label = "Open sub-agent" }) {
|
|
4594
|
+
return /* @__PURE__ */ jsx("button", {
|
|
4595
|
+
type: "button",
|
|
4596
|
+
className: "term-action",
|
|
4597
|
+
title: label,
|
|
4598
|
+
"aria-label": label,
|
|
4599
|
+
onClick: (event) => {
|
|
4600
|
+
event.stopPropagation();
|
|
4601
|
+
onOpen();
|
|
4602
|
+
},
|
|
4603
|
+
children: "⤢"
|
|
4604
|
+
});
|
|
4605
|
+
}
|
|
4582
4606
|
function CopyAction({ text, label = "Copy" }) {
|
|
4583
4607
|
const [copied, setCopied] = useState(false);
|
|
4584
4608
|
return /* @__PURE__ */ jsx("button", {
|
|
@@ -5021,6 +5045,32 @@ function scrollParent(from) {
|
|
|
5021
5045
|
return null;
|
|
5022
5046
|
}
|
|
5023
5047
|
//#endregion
|
|
5048
|
+
//#region src/components/terminal/image-box.ts
|
|
5049
|
+
/**
|
|
5050
|
+
* What the box says before the fetch lands.
|
|
5051
|
+
*
|
|
5052
|
+
* `bytes` is the decoded size the gateway stamped on the reference — the client
|
|
5053
|
+
* holds no bytes at all until it asks for them, so this is a number it cannot
|
|
5054
|
+
* compute, the same reason `total_chars` rides beside a truncated head.
|
|
5055
|
+
* `formatBytes` rather than a spelling of its own: the panel says "336.0 KB"
|
|
5056
|
+
* everywhere else, and a second byte formatter is a second thing to keep in
|
|
5057
|
+
* step.
|
|
5058
|
+
*/
|
|
5059
|
+
function imagePlaceholder(image) {
|
|
5060
|
+
return `image · ${formatBytes(image.bytes)}`;
|
|
5061
|
+
}
|
|
5062
|
+
/**
|
|
5063
|
+
* What it says when the fetch failed — a stale address after a dormant wake
|
|
5064
|
+
* (the route 404s rather than serving another call's pixels), a gateway too old
|
|
5065
|
+
* to know the route, a dropped connection.
|
|
5066
|
+
*
|
|
5067
|
+
* It occupies the same box, because the alternative is the row changing height
|
|
5068
|
+
* on a network failure. Silence is not an option here the way it is for
|
|
5069
|
+
* `HostImage`: that card names the host path in its result text, so a reader can
|
|
5070
|
+
* still find the picture; a replayed image part has no path to name.
|
|
5071
|
+
*/
|
|
5072
|
+
const IMAGE_UNAVAILABLE = "image unavailable";
|
|
5073
|
+
//#endregion
|
|
5024
5074
|
//#region src/components/terminal/result-preview.ts
|
|
5025
5075
|
/**
|
|
5026
5076
|
* How much of a tool result a collapsed row shows, and what it says it hid.
|
|
@@ -5056,7 +5106,16 @@ const PREVIEW_CHARS = 400;
|
|
|
5056
5106
|
* lines otherwise — a one-line JSON blob has no hidden lines to count, and
|
|
5057
5107
|
* "+0 lines" under a visibly cut-off row is worse than saying nothing.
|
|
5058
5108
|
*/
|
|
5059
|
-
|
|
5109
|
+
/**
|
|
5110
|
+
* `totalChars` is the **untruncated** length when the replay delivered only a
|
|
5111
|
+
* head (protocol's `ToolResultBlock.total_chars`). Passing it is not cosmetic:
|
|
5112
|
+
* computed from the head this row would say "… +7,600 chars" where the truth is
|
|
5113
|
+
* 641,003, and the wrong string is a *different pixel height* — which is exactly
|
|
5114
|
+
* the drift this module exists to prevent, since `height.ts` sizes the row by
|
|
5115
|
+
* wrapping this same text. Omitted for a whole result, where the lines are the
|
|
5116
|
+
* whole truth.
|
|
5117
|
+
*/
|
|
5118
|
+
function collapsedResult(lines, totalChars) {
|
|
5060
5119
|
const shown = [];
|
|
5061
5120
|
let chars = 0;
|
|
5062
5121
|
let cut = false;
|
|
@@ -5071,9 +5130,15 @@ function collapsedResult(lines) {
|
|
|
5071
5130
|
shown.push(line);
|
|
5072
5131
|
chars += line.length + 1;
|
|
5073
5132
|
}
|
|
5133
|
+
const held = lines.join("\n").length;
|
|
5134
|
+
const total = totalChars ?? held;
|
|
5074
5135
|
if (cut) return {
|
|
5075
5136
|
shown,
|
|
5076
|
-
more: `… +${(
|
|
5137
|
+
more: `… +${(total - chars).toLocaleString()} chars`
|
|
5138
|
+
};
|
|
5139
|
+
if (totalChars !== void 0 && total > held) return {
|
|
5140
|
+
shown,
|
|
5141
|
+
more: `… +${(total - chars).toLocaleString()} chars`
|
|
5077
5142
|
};
|
|
5078
5143
|
const hidden = lines.length - shown.length;
|
|
5079
5144
|
return {
|
|
@@ -5082,6 +5147,157 @@ function collapsedResult(lines) {
|
|
|
5082
5147
|
};
|
|
5083
5148
|
}
|
|
5084
5149
|
//#endregion
|
|
5150
|
+
//#region src/components/agent/tool-result-fetch.tsx
|
|
5151
|
+
const FetchContext = createContext(async () => false);
|
|
5152
|
+
function ToolResultFetchProvider({ value, children }) {
|
|
5153
|
+
return /* @__PURE__ */ jsx(FetchContext.Provider, {
|
|
5154
|
+
value: value ?? noop$1,
|
|
5155
|
+
children
|
|
5156
|
+
});
|
|
5157
|
+
}
|
|
5158
|
+
const noop$1 = async () => false;
|
|
5159
|
+
function useToolResultFetcher() {
|
|
5160
|
+
return useContext(FetchContext);
|
|
5161
|
+
}
|
|
5162
|
+
//#endregion
|
|
5163
|
+
//#region src/components/agent/tool-result-image.tsx
|
|
5164
|
+
const noop = async () => void 0;
|
|
5165
|
+
const ImageContext = createContext(noop);
|
|
5166
|
+
function ToolResultImageProvider({ value, children }) {
|
|
5167
|
+
return /* @__PURE__ */ jsx(ImageContext.Provider, {
|
|
5168
|
+
value: value ?? noop,
|
|
5169
|
+
children
|
|
5170
|
+
});
|
|
5171
|
+
}
|
|
5172
|
+
function useToolResultImageLoader() {
|
|
5173
|
+
return useContext(ImageContext);
|
|
5174
|
+
}
|
|
5175
|
+
/**
|
|
5176
|
+
* Long enough that a fast scrub through an image-heavy session fetches nothing
|
|
5177
|
+
* it flew past, short enough to be invisible to a reader who stopped.
|
|
5178
|
+
*
|
|
5179
|
+
* There is no second visibility system here on purpose: the transcript is
|
|
5180
|
+
* virtualized, so a *mounted* row is by definition within an overscan of the
|
|
5181
|
+
* viewport — the virtualizer already is the IntersectionObserver, and a second
|
|
5182
|
+
* answer to a question that has one is how the two disagree.
|
|
5183
|
+
*/
|
|
5184
|
+
const MOUNT_SETTLE_MS = 150;
|
|
5185
|
+
/**
|
|
5186
|
+
* One box's load, for either theme.
|
|
5187
|
+
*
|
|
5188
|
+
* Fires once the row has been mounted for {@link MOUNT_SETTLE_MS}, and then
|
|
5189
|
+
* **runs to completion** — an aborted fetch re-pays the whole image on the
|
|
5190
|
+
* return visit, and the gateway is HTTP/1.1, so the browser's per-origin
|
|
5191
|
+
* connection cap is the concurrency throttle for free.
|
|
5192
|
+
*
|
|
5193
|
+
* The effect keys on the address's *primitives*, never on the ref object: the
|
|
5194
|
+
* reducer replaces items on every streamed delta, so an object-identity dep
|
|
5195
|
+
* would re-run this on every token of the turn after it.
|
|
5196
|
+
*/
|
|
5197
|
+
function useToolResultImageSrc(ref) {
|
|
5198
|
+
const load = useToolResultImageLoader();
|
|
5199
|
+
const [state, setState] = useState({ failed: false });
|
|
5200
|
+
const { toolUseId, sourceSeq, partIndex, mediaType, bytes } = ref;
|
|
5201
|
+
useEffect(() => {
|
|
5202
|
+
let live = true;
|
|
5203
|
+
setState({ failed: false });
|
|
5204
|
+
const timer = setTimeout(() => {
|
|
5205
|
+
load({
|
|
5206
|
+
toolUseId,
|
|
5207
|
+
sourceSeq,
|
|
5208
|
+
partIndex,
|
|
5209
|
+
mediaType,
|
|
5210
|
+
bytes
|
|
5211
|
+
}).then((src) => {
|
|
5212
|
+
if (live) setState({
|
|
5213
|
+
src,
|
|
5214
|
+
failed: src === void 0
|
|
5215
|
+
});
|
|
5216
|
+
}).catch(() => {
|
|
5217
|
+
if (live) setState({ failed: true });
|
|
5218
|
+
});
|
|
5219
|
+
}, MOUNT_SETTLE_MS);
|
|
5220
|
+
return () => {
|
|
5221
|
+
live = false;
|
|
5222
|
+
clearTimeout(timer);
|
|
5223
|
+
};
|
|
5224
|
+
}, [
|
|
5225
|
+
load,
|
|
5226
|
+
toolUseId,
|
|
5227
|
+
sourceSeq,
|
|
5228
|
+
partIndex,
|
|
5229
|
+
mediaType,
|
|
5230
|
+
bytes
|
|
5231
|
+
]);
|
|
5232
|
+
return state;
|
|
5233
|
+
}
|
|
5234
|
+
/** ~64 MB of decoded pictures held at once. At the corpus's 335 KB median that
|
|
5235
|
+
* is ~190 images, which no viewport holds; the budget exists so a session
|
|
5236
|
+
* scrolled end to end does not pin every screenshot it passed. */
|
|
5237
|
+
const CACHE_BUDGET_BYTES = 64 * 1024 * 1024;
|
|
5238
|
+
/**
|
|
5239
|
+
* `useHostImage`'s shape, generalized to the replay route — and **bounded**,
|
|
5240
|
+
* which `useHostImage` is not.
|
|
5241
|
+
*
|
|
5242
|
+
* The promise-per-key cache is what makes this callable from a transcript row at
|
|
5243
|
+
* all: rows re-render on every streamed delta, and an uncached resolver would
|
|
5244
|
+
* re-fetch each time. The LRU is the part that is new. Object URLs pin their
|
|
5245
|
+
* blob until revoked, so a fully-scrolled hundred-image session would otherwise
|
|
5246
|
+
* hold ~50 MB until the panel unmounted — and evicting means revoking, or the
|
|
5247
|
+
* eviction frees a `Map` entry and nothing else.
|
|
5248
|
+
*
|
|
5249
|
+
* Re-fetching on a return scroll is fine, and is the whole design: the bytes are
|
|
5250
|
+
* one authenticated request away, which is precisely what makes it cheap not to
|
|
5251
|
+
* have shipped them in the attach.
|
|
5252
|
+
*/
|
|
5253
|
+
function useToolResultImages(client, sessionId) {
|
|
5254
|
+
const cache = useRef(/* @__PURE__ */ new Map());
|
|
5255
|
+
useEffect(() => () => {
|
|
5256
|
+
for (const entry of cache.current.values()) if (entry.url) URL.revokeObjectURL(entry.url);
|
|
5257
|
+
cache.current.clear();
|
|
5258
|
+
}, []);
|
|
5259
|
+
return useCallback((ref) => {
|
|
5260
|
+
if (!sessionId) return Promise.resolve(void 0);
|
|
5261
|
+
const key = `${sessionId}:${ref.sourceSeq}:${ref.toolUseId}:${ref.partIndex}`;
|
|
5262
|
+
const hit = cache.current.get(key);
|
|
5263
|
+
if (hit) {
|
|
5264
|
+
cache.current.delete(key);
|
|
5265
|
+
cache.current.set(key, hit);
|
|
5266
|
+
return hit.pending;
|
|
5267
|
+
}
|
|
5268
|
+
const pending = client.toolResultImage(sessionId, ref.sourceSeq, ref.toolUseId, ref.partIndex).then((blob) => {
|
|
5269
|
+
if (blob.size === 0) return void 0;
|
|
5270
|
+
const url = URL.createObjectURL(blob);
|
|
5271
|
+
const entry = cache.current.get(key);
|
|
5272
|
+
if (entry) {
|
|
5273
|
+
entry.url = url;
|
|
5274
|
+
entry.bytes = blob.size;
|
|
5275
|
+
evict(cache.current, key);
|
|
5276
|
+
} else URL.revokeObjectURL(url);
|
|
5277
|
+
return url;
|
|
5278
|
+
}).catch(() => void 0);
|
|
5279
|
+
cache.current.set(key, {
|
|
5280
|
+
pending,
|
|
5281
|
+
bytes: ref.bytes
|
|
5282
|
+
});
|
|
5283
|
+
return pending;
|
|
5284
|
+
}, [client, sessionId]);
|
|
5285
|
+
}
|
|
5286
|
+
/** Drop oldest-first until the held bytes fit the budget, revoking as it goes.
|
|
5287
|
+
* `keep` is the entry just resolved — evicting the picture a row is about to
|
|
5288
|
+
* draw would be a fetch spent on nothing. */
|
|
5289
|
+
function evict(cache, keep) {
|
|
5290
|
+
let held = 0;
|
|
5291
|
+
for (const entry of cache.values()) held += entry.bytes;
|
|
5292
|
+
for (const [key, entry] of cache) {
|
|
5293
|
+
if (held <= CACHE_BUDGET_BYTES) return;
|
|
5294
|
+
if (key === keep) continue;
|
|
5295
|
+
if (entry.url) URL.revokeObjectURL(entry.url);
|
|
5296
|
+
cache.delete(key);
|
|
5297
|
+
held -= entry.bytes;
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
//#endregion
|
|
5085
5301
|
//#region src/components/terminal/tool-run.ts
|
|
5086
5302
|
/**
|
|
5087
5303
|
* What breaks a run.
|
|
@@ -5138,8 +5354,305 @@ function runSummary(items, busy) {
|
|
|
5138
5354
|
const breakdown = [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([family, count]) => `${count} ${family}`).join(", ");
|
|
5139
5355
|
return `${verb}${n} tool${n === 1 ? "" : "s"} · ${breakdown}${tail}`;
|
|
5140
5356
|
}
|
|
5357
|
+
const clip = (text, max = 80) => text.length > max ? text.slice(0, max - 1) + "…" : text;
|
|
5358
|
+
const trimmed = (value) => typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
5359
|
+
/**
|
|
5360
|
+
* The row's identity half: which task this is.
|
|
5361
|
+
*
|
|
5362
|
+
* The Claude SDK's `Task` input carries `subagent_type` (e.g. "Explore") and a
|
|
5363
|
+
* 3–5 word `description`, and both are worth the line: parallel tasks are the
|
|
5364
|
+
* whole reason the block exists, and two rows both reading `Task(…)` answer
|
|
5365
|
+
* nothing. `Task(Explore · find the auth check)` — falling back to the
|
|
5366
|
+
* ordinary input preview when an engine sends neither, so the header is never
|
|
5367
|
+
* emptier than a plain tool row's.
|
|
5368
|
+
*/
|
|
5369
|
+
function taskLabel(task) {
|
|
5370
|
+
return `${task.name}(${taskIdentity(task)})`;
|
|
5371
|
+
}
|
|
5372
|
+
/**
|
|
5373
|
+
* The inner half of {@link taskLabel} — `Explore · find the auth check` — without
|
|
5374
|
+
* the `Task(…)` wrapper.
|
|
5375
|
+
*
|
|
5376
|
+
* Split out for the sub-agent takeover's header, which names the agent it is
|
|
5377
|
+
* showing and has no room (or reason) to repeat the tool's own name: the whole
|
|
5378
|
+
* surface *is* that Task. Extracted rather than re-spelled so the header and the
|
|
5379
|
+
* row it was opened from cannot drift, and so both keep matching protocol's
|
|
5380
|
+
* `subagentLabel`, which reads the same two input fields for the sessions list.
|
|
5381
|
+
*/
|
|
5382
|
+
function taskIdentity(task) {
|
|
5383
|
+
const input = task.input;
|
|
5384
|
+
const description = trimmed(input?.description);
|
|
5385
|
+
const agent = trimmed(input?.subagent_type);
|
|
5386
|
+
return agent && description ? `${agent} · ${clip(description)}` : agent ?? (description ? clip(description) : toolInputPreview(task.input));
|
|
5387
|
+
}
|
|
5388
|
+
/**
|
|
5389
|
+
* **What this agent was actually asked** — the sub-agent's brief, or undefined
|
|
5390
|
+
* when the engine did not give us one.
|
|
5391
|
+
*
|
|
5392
|
+
* Measured against a live session rather than assumed: the Agent SDK's call
|
|
5393
|
+
* carries `{description, subagent_type, run_in_background, prompt}`, and
|
|
5394
|
+
* `prompt` is the instruction.
|
|
5395
|
+
*
|
|
5396
|
+
* **Whether it also arrives in the stream depends on the agent.** A foreground
|
|
5397
|
+
* `Task` forwards its brief as a real nested `user_message` (that is what
|
|
5398
|
+
* `forwardSubagentText` buys and what the reducer stamps a `parentToolUseId`
|
|
5399
|
+
* on), so `subagentItems` picks it up and the frame already has it. A
|
|
5400
|
+
* **background** agent does not: measured on a session with eight of them,
|
|
5401
|
+
* zero items carried a `parentToolUseId` on a `user` kind. Those runs are
|
|
5402
|
+
* exactly the ones you open a takeover on — an agent working while you read
|
|
5403
|
+
* something else — and their brief was nowhere at all.
|
|
5404
|
+
*
|
|
5405
|
+
* So this is the *fallback*, not the source: the callers splice it in only when
|
|
5406
|
+
* the frame carries no brief of its own, or the same instruction would be drawn
|
|
5407
|
+
* twice.
|
|
5408
|
+
*
|
|
5409
|
+
* `description` is deliberately not a fallback: it is the 3–5 word label the
|
|
5410
|
+
* header already prints, and repeating it as a brief would claim we know the
|
|
5411
|
+
* instruction when we do not. **Codex genuinely has none** — its `spawn_agent`
|
|
5412
|
+
* message is an encrypted blob on the wire — so on that engine this is
|
|
5413
|
+
* undefined and the row is not drawn, rather than drawn empty.
|
|
5414
|
+
*/
|
|
5415
|
+
function taskBrief(task) {
|
|
5416
|
+
const input = task.input;
|
|
5417
|
+
return trimmed(input?.prompt);
|
|
5418
|
+
}
|
|
5419
|
+
const callBusy = (call) => call.status === "running" || call.status === "pending";
|
|
5420
|
+
/** Did this one call fail? Both spellings are needed: an out-of-loop execution
|
|
5421
|
+
* failure sets `status` with no `is_error` block to read, and an engine can flag
|
|
5422
|
+
* `is_error` on a call the reducer has not settled yet. */
|
|
5423
|
+
const callFailed = (call) => call.status === "failed" || call.result?.isError === true;
|
|
5424
|
+
/**
|
|
5425
|
+
* Does a folded run colour red? **Only when its last call failed.**
|
|
5426
|
+
*
|
|
5427
|
+
* It used to be `some`, on the argument that a failure should colour the block
|
|
5428
|
+
* rather than fragment it. The argument was right about not fragmenting and
|
|
5429
|
+
* wrong about `some`: a run is a sequence the model worked through, and a
|
|
5430
|
+
* failure it recovered from two calls later is how work goes — a grep that
|
|
5431
|
+
* matched nothing, a build fixed on the second go. Reddening the whole run for
|
|
5432
|
+
* it means a normal working session is painted red, which spends the colour
|
|
5433
|
+
* that should have been left for the one thing still broken.
|
|
5434
|
+
*
|
|
5435
|
+
* The last call is the run's *outcome*, and an outcome is what a collapsed row
|
|
5436
|
+
* can honestly claim. The failures inside it are not hidden — they are one
|
|
5437
|
+
* press away, each red on its own row, and the recap counts every one. The
|
|
5438
|
+
* **scrubber agrees with this rule** rather than overriding it: it marks a
|
|
5439
|
+
* failed call only when the call is its row's outcome, which for a run is
|
|
5440
|
+
* exactly this one. It used to mark every member on the argument that the
|
|
5441
|
+
* rail asks a different question; against a real session that was nine alarms
|
|
5442
|
+
* on the rail for a transcript reddening one row.
|
|
5443
|
+
*/
|
|
5444
|
+
function runFailed(items) {
|
|
5445
|
+
const last = items[items.length - 1];
|
|
5446
|
+
return last !== void 0 && callFailed(last);
|
|
5447
|
+
}
|
|
5448
|
+
/** Is anything inside still going? The call itself, normally — the Task
|
|
5449
|
+
* settles only when its subagent finishes — but a bridged or deferred child
|
|
5450
|
+
* can outlive it, and a pulse that stopped while a child still worked would
|
|
5451
|
+
* read as a hang. */
|
|
5452
|
+
function taskBusy(task, children) {
|
|
5453
|
+
return callBusy(task) || children.some((child) => child.kind === "tool_call" && callBusy(child));
|
|
5454
|
+
}
|
|
5455
|
+
/**
|
|
5456
|
+
* Does the row colour red? **The task's own outcome, and nothing else.**
|
|
5457
|
+
*
|
|
5458
|
+
* It used to be "or any child call's", which does not survive contact with a
|
|
5459
|
+
* real subagent: an agent that ran a hundred calls, one of them a grep that
|
|
5460
|
+
* matched nothing, came back with a red line saying it had failed. It had not —
|
|
5461
|
+
* it had done exactly what it was asked, and the transcript said otherwise in
|
|
5462
|
+
* the one colour reserved for things that need a human.
|
|
5463
|
+
*
|
|
5464
|
+
* This is the call `SubagentInfo.status` already makes, and it made it for this
|
|
5465
|
+
* reason (see `packages/protocol`): the sub-agent's **own** `tool_result`
|
|
5466
|
+
* `is_error`, deliberately not `taskFailed`. The argument there was that a
|
|
5467
|
+
* nothing-matched grep must not read as a failed run *beside a session name*;
|
|
5468
|
+
* what a hundred-call agent shows is that it must not read that way beside the
|
|
5469
|
+
* `Task` row either. Two surfaces, one rule, one spelling.
|
|
5470
|
+
*
|
|
5471
|
+
* Nothing is concealed by this. A failed child is red on its own row, one press
|
|
5472
|
+
* away, and the recap counts it. The **scrubber follows this rule too** and no
|
|
5473
|
+
* longer marks such a child: a red tick on the rail says precisely what this
|
|
5474
|
+
* row is forbidden from saying. The sub-agent band still says an agent ran
|
|
5475
|
+
* here, and the task's own red tick still says it came back broken — which is
|
|
5476
|
+
* what the two channels are for.
|
|
5477
|
+
*/
|
|
5478
|
+
function taskFailed(task) {
|
|
5479
|
+
return callFailed(task);
|
|
5480
|
+
}
|
|
5481
|
+
/**
|
|
5482
|
+
* The collapsed task row's one line: identity, then scale.
|
|
5483
|
+
*
|
|
5484
|
+
* `Task(Explore · find the auth check) · 7 tools…` while the subagent works —
|
|
5485
|
+
* the count grows as it does, which is the row's progress reading, and the
|
|
5486
|
+
* trailing ellipsis is the same in-flight signal `runSummary` uses (the pulse
|
|
5487
|
+
* in the gutter carries the beat). Settled, the ellipsis drops:
|
|
5488
|
+
* `… · 7 tools`. "Tools" and not "tool calls" because `runSummary` already
|
|
5489
|
+
* chose that word for the same count one row over.
|
|
5490
|
+
*
|
|
5491
|
+
* The counts are counted from the absorbed children, never read from the
|
|
5492
|
+
* engine's structured Task output — WorkerDeck does not plumb structured tool
|
|
5493
|
+
* results to clients, so a transcript replayed tomorrow must spell the same
|
|
5494
|
+
* line from the same items it holds today.
|
|
5495
|
+
*
|
|
5496
|
+
* With no tool calls yet — the subagent thinking, or only its brief arrived —
|
|
5497
|
+
* the line says `working…`, because `0 tools…` reads as a stall; settled with
|
|
5498
|
+
* none it says `done`.
|
|
5499
|
+
*/
|
|
5500
|
+
function taskSummary(task, children) {
|
|
5501
|
+
const busy = taskBusy(task, children);
|
|
5502
|
+
const calls = children.reduce((n, child) => n + (child.kind === "tool_call" ? 1 : 0), 0);
|
|
5503
|
+
const label = taskLabel(task);
|
|
5504
|
+
if (calls === 0) return busy ? `${label} · working…` : `${label} · done`;
|
|
5505
|
+
return `${label} · ${calls} tool${calls === 1 ? "" : "s"}${busy ? "…" : ""}`;
|
|
5506
|
+
}
|
|
5507
|
+
//#endregion
|
|
5508
|
+
//#region src/components/terminal/blocks.ts
|
|
5509
|
+
/**
|
|
5510
|
+
* The id of the tool call this item was produced inside, or `undefined` at the
|
|
5511
|
+
* top level. One spelling for both shapes the reducer emits: `assistant_text`
|
|
5512
|
+
* / `thinking` / `tool_call` carry `parentToolUseId: string | null` on every
|
|
5513
|
+
* instance, while `user` carries it **optionally** (a human prompt has no
|
|
5514
|
+
* parent at all — the key exists only on a subagent's brief). Callers must go
|
|
5515
|
+
* through this rather than reading the field, or the absent-key case silently
|
|
5516
|
+
* types as a compile error on one kind and a miss on another.
|
|
5517
|
+
*/
|
|
5518
|
+
function parentOf(item) {
|
|
5519
|
+
return ("parentToolUseId" in item ? item.parentToolUseId : void 0) ?? void 0;
|
|
5520
|
+
}
|
|
5521
|
+
/**
|
|
5522
|
+
* **The frame membership rule**: the items a sub-agent produced, and nothing
|
|
5523
|
+
* else — what the takeover renders instead of the whole conversation.
|
|
5524
|
+
*
|
|
5525
|
+
* One exported function rather than a `filter` at each call site, because this
|
|
5526
|
+
* is a rule two renderers have to agree on: iOS mirrors the terminal model out
|
|
5527
|
+
* of this module, and a phone that framed a sub-agent slightly differently would
|
|
5528
|
+
* be a second answer to "what is this agent doing".
|
|
5529
|
+
*
|
|
5530
|
+
* It picks up the brief (a `user` item *with* a parent), the thinking, the
|
|
5531
|
+
* streamed text (deltas are namespaced per sidechain, so `streaming:<parentId>`
|
|
5532
|
+
* carries the parent like any other item), every tool call with its result, and
|
|
5533
|
+
* the final report. It excludes the spawning `Task` call itself — that is the
|
|
5534
|
+
* frame, not a row in it — and every other agent's work.
|
|
5535
|
+
*
|
|
5536
|
+
* The slice is safe to hand straight to {@link terminalBlocks} at offset 0:
|
|
5537
|
+
* nothing in it is top-level, so nothing absorbs, and consecutive calls still
|
|
5538
|
+
* fold into runs because {@link foldsTogether} keys on an *equal* parent rather
|
|
5539
|
+
* than on absence of one. Row indices are internally consistent because the rows
|
|
5540
|
+
* and the `items` they came from are the same array.
|
|
5541
|
+
*/
|
|
5542
|
+
function subagentItems(items, parentToolUseId) {
|
|
5543
|
+
return items.filter((item) => parentOf(item) === parentToolUseId);
|
|
5544
|
+
}
|
|
5545
|
+
/** Is this a row the transcript folds into a run? Any tool call is — see
|
|
5546
|
+
* `tool-run.ts` for why this is no longer shell-only. */
|
|
5547
|
+
function isRunCall(item) {
|
|
5548
|
+
return item.kind === "tool_call";
|
|
5549
|
+
}
|
|
5550
|
+
/** The absorbed items, flat and in stream order — what `taskSummary` counts
|
|
5551
|
+
* and the collapsed row's one line is built from. */
|
|
5552
|
+
function taskChildItems(block) {
|
|
5553
|
+
return block.children.flatMap((child) => "run" in child ? child.run : [child.item]);
|
|
5554
|
+
}
|
|
5555
|
+
/** Append one item to a leaf-block list, folding it into the previous run when
|
|
5556
|
+
* the membership rule allows — the one fold implementation, used for the
|
|
5557
|
+
* top-level stream and for each task's children alike. */
|
|
5558
|
+
function pushLeaf(out, item, index) {
|
|
5559
|
+
const previous = out.at(-1);
|
|
5560
|
+
if (isRunCall(item)) {
|
|
5561
|
+
if (previous && "run" in previous && foldsTogether(previous.run[0], item)) {
|
|
5562
|
+
previous.run.push(item);
|
|
5563
|
+
previous.indices.push(index);
|
|
5564
|
+
} else out.push({
|
|
5565
|
+
key: `run:${item.id}`,
|
|
5566
|
+
run: [item],
|
|
5567
|
+
indices: [index],
|
|
5568
|
+
index
|
|
5569
|
+
});
|
|
5570
|
+
return;
|
|
5571
|
+
}
|
|
5572
|
+
out.push({
|
|
5573
|
+
key: `${item.kind}:${item.id}`,
|
|
5574
|
+
item,
|
|
5575
|
+
index
|
|
5576
|
+
});
|
|
5577
|
+
}
|
|
5578
|
+
/**
|
|
5579
|
+
* @param offset What `items[0]`'s index is in the whole transcript — the
|
|
5580
|
+
* virtualized shell folds each side of the recap boundary separately, and the
|
|
5581
|
+
* rows still have to say where they sit for the catch-up dimming.
|
|
5582
|
+
* @param fold Whether to group at all. `false` gives one block per item —
|
|
5583
|
+
* no runs *and no task absorption* — which is what the cards variant
|
|
5584
|
+
* renders: this is the terminal theme's rule and must not silently reshape
|
|
5585
|
+
* another renderer's row list.
|
|
5586
|
+
*/
|
|
5587
|
+
function terminalBlocks(items, offset = 0, fold = true) {
|
|
5588
|
+
if (!fold) return items.map((item, position) => ({
|
|
5589
|
+
key: `${item.kind}:${item.id}`,
|
|
5590
|
+
item,
|
|
5591
|
+
index: offset + position
|
|
5592
|
+
}));
|
|
5593
|
+
const topLevelCalls = /* @__PURE__ */ new Set();
|
|
5594
|
+
for (const item of items) if (item.kind === "tool_call" && parentOf(item) === void 0) topLevelCalls.add(item.id);
|
|
5595
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
5596
|
+
items.forEach((item, position) => {
|
|
5597
|
+
const parent = parentOf(item);
|
|
5598
|
+
if (parent !== void 0 && topLevelCalls.has(parent)) {
|
|
5599
|
+
const list = childrenOf.get(parent);
|
|
5600
|
+
if (list) list.push({
|
|
5601
|
+
item,
|
|
5602
|
+
index: offset + position
|
|
5603
|
+
});
|
|
5604
|
+
else childrenOf.set(parent, [{
|
|
5605
|
+
item,
|
|
5606
|
+
index: offset + position
|
|
5607
|
+
}]);
|
|
5608
|
+
}
|
|
5609
|
+
});
|
|
5610
|
+
const out = [];
|
|
5611
|
+
for (const [position, item] of items.entries()) {
|
|
5612
|
+
const index = offset + position;
|
|
5613
|
+
const parent = parentOf(item);
|
|
5614
|
+
if (parent !== void 0 && childrenOf.has(parent)) continue;
|
|
5615
|
+
if (item.kind === "tool_call") {
|
|
5616
|
+
const children = childrenOf.get(item.id);
|
|
5617
|
+
if (children) {
|
|
5618
|
+
const folded = [];
|
|
5619
|
+
for (const child of children) pushLeaf(folded, child.item, child.index);
|
|
5620
|
+
out.push({
|
|
5621
|
+
key: `task:${item.id}`,
|
|
5622
|
+
task: item,
|
|
5623
|
+
children: folded,
|
|
5624
|
+
childIndices: children.map((child) => child.index),
|
|
5625
|
+
index
|
|
5626
|
+
});
|
|
5627
|
+
continue;
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5630
|
+
pushLeaf(out, item, index);
|
|
5631
|
+
}
|
|
5632
|
+
return out;
|
|
5633
|
+
}
|
|
5634
|
+
/** Spacing between two items: a blank line, unless the pair belongs together.
|
|
5635
|
+
* Tool output already sits under its call, and a run of tool calls reads as one
|
|
5636
|
+
* block — the CLI leaves no blank line inside either. */
|
|
5637
|
+
function needsBlank(previous, next) {
|
|
5638
|
+
if (previous.kind === "tool_call" && next.kind === "tool_call") return false;
|
|
5639
|
+
return true;
|
|
5640
|
+
}
|
|
5641
|
+
/** The same rule over blocks: a run counts as the tool calls it folded, and a
|
|
5642
|
+
* task block counts as the `Task` call it stands for — a collapsed task row
|
|
5643
|
+
* sits flush with the tool rows of the same turn, exactly as the call itself
|
|
5644
|
+
* did before it grew children. */
|
|
5645
|
+
function blockNeedsBlank(previous, next) {
|
|
5646
|
+
const kind = (block) => "item" in block ? block.item.kind : "tool_call";
|
|
5647
|
+
return !(kind(previous) === "tool_call" && kind(next) === "tool_call");
|
|
5648
|
+
}
|
|
5141
5649
|
/** How much the expanded row shows before offering the rest. The collapsed
|
|
5142
|
-
* budget is `collapsedResult`'s, shared with the height calculator.
|
|
5650
|
+
* budget is `collapsedResult`'s, shared with the height calculator.
|
|
5651
|
+
*
|
|
5652
|
+
* Exported for one test and not from the package: protocol's
|
|
5653
|
+
* `TOOL_RESULT_HEAD_CHARS` is chosen to exceed it, so that a truncated result's
|
|
5654
|
+
* open state is byte-identical to an untruncated one and only the uncapped
|
|
5655
|
+
* `full` press ever fetches. That relationship is asserted, not assumed. */
|
|
5143
5656
|
const RESULT_PREVIEW_CHARS$1 = 2e3;
|
|
5144
5657
|
/** Whole lines up to a character budget — never zero, because a single line
|
|
5145
5658
|
* longer than the budget still has to be shown or the row would open onto
|
|
@@ -5209,6 +5722,8 @@ const TOOL_TONE = {
|
|
|
5209
5722
|
function ToolRow({ item }) {
|
|
5210
5723
|
const [open, setOpen] = useState(false);
|
|
5211
5724
|
const [full, setFull] = useState(false);
|
|
5725
|
+
const [fetching, setFetching] = useState(false);
|
|
5726
|
+
const fetchResult = useToolResultFetcher();
|
|
5212
5727
|
const reveal = useRevealOnOpen(open);
|
|
5213
5728
|
const status = item.status ?? (item.result === void 0 ? "running" : "settled");
|
|
5214
5729
|
const busy = status === "running" || status === "pending";
|
|
@@ -5216,10 +5731,12 @@ function ToolRow({ item }) {
|
|
|
5216
5731
|
const pulse = usePulse(busy);
|
|
5217
5732
|
const text = item.result?.text ?? "";
|
|
5218
5733
|
const lines = text.trimEnd().split("\n");
|
|
5219
|
-
const collapsed = collapsedResult(lines);
|
|
5734
|
+
const collapsed = collapsedResult(lines, item.result?.totalChars);
|
|
5220
5735
|
const preview = open ? full ? lines : clipToChars(lines, RESULT_PREVIEW_CHARS$1) : collapsed.shown;
|
|
5221
5736
|
const hidden = lines.length - preview.length;
|
|
5222
5737
|
const clipped = open && !full && hidden > 0;
|
|
5738
|
+
const truncated = item.result?.truncated === true;
|
|
5739
|
+
const missing = truncated ? (item.result?.totalChars ?? 0) - text.length : 0;
|
|
5223
5740
|
const tone = isError ? "red" : status === "settled" && isMutatingTool(item.name) ? "green" : TOOL_TONE[status] ?? "dim";
|
|
5224
5741
|
const command = item.input?.command;
|
|
5225
5742
|
const copyable = typeof command === "string" ? command : text;
|
|
@@ -5231,76 +5748,123 @@ function ToolRow({ item }) {
|
|
|
5231
5748
|
text: copyable,
|
|
5232
5749
|
label: "Copy"
|
|
5233
5750
|
}) : null,
|
|
5234
|
-
children: [
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5751
|
+
children: [
|
|
5752
|
+
/* @__PURE__ */ jsx(Pressable, {
|
|
5753
|
+
onPress: () => setOpen((v) => !v),
|
|
5754
|
+
expanded: open,
|
|
5755
|
+
children: /* @__PURE__ */ jsxs(Row, {
|
|
5756
|
+
glyph: busy ? pulse : "●",
|
|
5757
|
+
glyphTone: tone,
|
|
5758
|
+
tone: "fg",
|
|
5759
|
+
children: [
|
|
5760
|
+
/* @__PURE__ */ jsx(Ink, {
|
|
5761
|
+
bold: true,
|
|
5762
|
+
tone: "bright",
|
|
5763
|
+
children: item.name
|
|
5764
|
+
}),
|
|
5765
|
+
/* @__PURE__ */ jsxs(Ink, {
|
|
5766
|
+
tone: "dim",
|
|
5767
|
+
children: [
|
|
5768
|
+
"(",
|
|
5769
|
+
toolInputPreview(item.input),
|
|
5770
|
+
")"
|
|
5771
|
+
]
|
|
5772
|
+
}),
|
|
5773
|
+
item.backend && item.backend !== "server" ? /* @__PURE__ */ jsxs(Ink, {
|
|
5774
|
+
tone: "faint",
|
|
5775
|
+
children: [" · ", item.backend]
|
|
5776
|
+
}) : null
|
|
5777
|
+
]
|
|
5778
|
+
})
|
|
5779
|
+
}),
|
|
5780
|
+
item.result?.images?.map((image) => /* @__PURE__ */ jsx(TerminalImage, {
|
|
5781
|
+
toolUseId: item.id,
|
|
5782
|
+
image
|
|
5783
|
+
}, image.partIndex)),
|
|
5784
|
+
item.patch && !open ? /* @__PURE__ */ jsx(TerminalDiff, { patch: item.patch }) : text ? /* @__PURE__ */ jsxs(Fragment$1, { children: [preview.map((line, index) => /* @__PURE__ */ jsx(Row, {
|
|
5785
|
+
indent: 1,
|
|
5786
|
+
columns: 3,
|
|
5787
|
+
glyph: index === 0 ? "⎿" : void 0,
|
|
5788
|
+
tone: isError ? "red" : "dim",
|
|
5789
|
+
children: line || "\xA0"
|
|
5790
|
+
}, index)), !open ? collapsed.more ? /* @__PURE__ */ jsx(Row, {
|
|
5791
|
+
indent: 1,
|
|
5792
|
+
columns: 3,
|
|
5793
|
+
tone: "faint",
|
|
5794
|
+
children: collapsed.more
|
|
5795
|
+
}) : null : clipped || truncated ? /* @__PURE__ */ jsx(Row, {
|
|
5796
|
+
indent: 1,
|
|
5797
|
+
columns: 3,
|
|
5798
|
+
tone: "faint",
|
|
5799
|
+
children: fetching ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
5800
|
+
"… fetching ",
|
|
5801
|
+
(item.result?.totalChars ?? 0).toLocaleString(),
|
|
5802
|
+
" chars"
|
|
5803
|
+
] }) : clipped || truncated ? /* @__PURE__ */ jsx("button", {
|
|
5804
|
+
type: "button",
|
|
5805
|
+
className: "term-press term-link",
|
|
5806
|
+
onClick: () => {
|
|
5807
|
+
setFull(true);
|
|
5808
|
+
if (!truncated) return;
|
|
5809
|
+
setFetching(true);
|
|
5810
|
+
fetchResult(item.id).finally(() => setFetching(false));
|
|
5811
|
+
},
|
|
5812
|
+
children: truncated ? `… +${missing.toLocaleString()} chars — fetch the rest` : `… +${hidden} line${hidden === 1 ? "" : "s"} — show all ${text.length.toLocaleString()} chars`
|
|
5813
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
5814
|
+
"… +",
|
|
5815
|
+
hidden,
|
|
5816
|
+
" line",
|
|
5817
|
+
hidden === 1 ? "" : "s"
|
|
5818
|
+
] })
|
|
5819
|
+
}) : hidden > 0 ? /* @__PURE__ */ jsxs(Row, {
|
|
5820
|
+
indent: 1,
|
|
5821
|
+
columns: 3,
|
|
5822
|
+
tone: "faint",
|
|
5280
5823
|
children: [
|
|
5281
5824
|
"… +",
|
|
5282
5825
|
hidden,
|
|
5283
5826
|
" line",
|
|
5284
|
-
hidden === 1 ? "" : "s"
|
|
5285
|
-
" — show all",
|
|
5286
|
-
" ",
|
|
5287
|
-
text.length.toLocaleString(),
|
|
5288
|
-
" chars"
|
|
5827
|
+
hidden === 1 ? "" : "s"
|
|
5289
5828
|
]
|
|
5290
|
-
}) :
|
|
5291
|
-
|
|
5292
|
-
hidden,
|
|
5293
|
-
" line",
|
|
5294
|
-
hidden === 1 ? "" : "s"
|
|
5295
|
-
] })
|
|
5296
|
-
}) : null] }) : null]
|
|
5829
|
+
}) : null] }) : null
|
|
5830
|
+
]
|
|
5297
5831
|
})
|
|
5298
5832
|
});
|
|
5299
5833
|
}
|
|
5300
|
-
/**
|
|
5301
|
-
*
|
|
5302
|
-
|
|
5303
|
-
|
|
5834
|
+
/**
|
|
5835
|
+
* A picture a tool returned, in a box of {@link IMAGE_BOX_LINES} whole lines.
|
|
5836
|
+
*
|
|
5837
|
+
* **Three states, one height.** Before the fetch lands the box is a wash and the
|
|
5838
|
+
* size the gateway declared; after it, the picture, letterboxed inside the same
|
|
5839
|
+
* box; on a refusal, `image unavailable` in it. Nothing here may ever collapse
|
|
5840
|
+
* to nothing — that is `HostImage`'s return-null-then-pop, which in a
|
|
5841
|
+
* *virtualized* list is not a flicker but a reflow of every row below it, and
|
|
5842
|
+
* the height calculator would have been lying about the row from plan time.
|
|
5843
|
+
*
|
|
5844
|
+
* The box is why the calculator can stay exact: it is a constant, not a function
|
|
5845
|
+
* of pixels nobody has downloaded yet.
|
|
5846
|
+
*/
|
|
5847
|
+
function TerminalImage({ toolUseId, image }) {
|
|
5848
|
+
const { src, failed } = useToolResultImageSrc({
|
|
5849
|
+
toolUseId,
|
|
5850
|
+
...image
|
|
5851
|
+
});
|
|
5852
|
+
return /* @__PURE__ */ jsx(Row, {
|
|
5853
|
+
indent: 1,
|
|
5854
|
+
columns: 3,
|
|
5855
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
5856
|
+
className: "term-image",
|
|
5857
|
+
"data-state": src ? "loaded" : failed ? "failed" : "pending",
|
|
5858
|
+
style: { height: `calc(var(--term-line) * 12)` },
|
|
5859
|
+
children: src ? /* @__PURE__ */ jsx("img", {
|
|
5860
|
+
src,
|
|
5861
|
+
alt: imagePlaceholder(image)
|
|
5862
|
+
}) : /* @__PURE__ */ jsx(Ink, {
|
|
5863
|
+
tone: "faint",
|
|
5864
|
+
children: failed ? IMAGE_UNAVAILABLE : imagePlaceholder(image)
|
|
5865
|
+
})
|
|
5866
|
+
})
|
|
5867
|
+
});
|
|
5304
5868
|
}
|
|
5305
5869
|
/**
|
|
5306
5870
|
* A run of tool calls, as one line.
|
|
@@ -5312,10 +5876,11 @@ function isRunCall(item) {
|
|
|
5312
5876
|
* collapses to its count and gets out of the way — and opens, in full, the
|
|
5313
5877
|
* moment it is the thing you actually want.
|
|
5314
5878
|
*
|
|
5315
|
-
* The membership and
|
|
5316
|
-
* calculator. A
|
|
5317
|
-
*
|
|
5318
|
-
* the run
|
|
5879
|
+
* The membership, wording and failure rules live in `tool-run.ts`, shared with
|
|
5880
|
+
* the height calculator. A failure never breaks the run — fragmenting it around
|
|
5881
|
+
* one would hide the failure in a longer list rather than surface it — but only
|
|
5882
|
+
* the run's **last** call colours it, because that is the run's outcome and an
|
|
5883
|
+
* outcome is what a collapsed row can honestly claim (see `runFailed`).
|
|
5319
5884
|
*/
|
|
5320
5885
|
function ToolRunRow({ items }) {
|
|
5321
5886
|
const [open, setOpen] = useState(false);
|
|
@@ -5324,7 +5889,7 @@ function ToolRunRow({ items }) {
|
|
|
5324
5889
|
const status = item.status ?? (item.result === void 0 ? "running" : "settled");
|
|
5325
5890
|
return status === "running" || status === "pending";
|
|
5326
5891
|
});
|
|
5327
|
-
const failed = items
|
|
5892
|
+
const failed = runFailed(items);
|
|
5328
5893
|
const pulse = usePulse(busy);
|
|
5329
5894
|
return /* @__PURE__ */ jsxs("div", {
|
|
5330
5895
|
ref: reveal,
|
|
@@ -5341,36 +5906,6 @@ function ToolRunRow({ items }) {
|
|
|
5341
5906
|
}), open ? /* @__PURE__ */ jsx("div", { children: items.map((item) => /* @__PURE__ */ jsx(ToolRow, { item }, item.id)) }) : null]
|
|
5342
5907
|
});
|
|
5343
5908
|
}
|
|
5344
|
-
/**
|
|
5345
|
-
* @param offset What `items[0]`'s index is in the whole transcript — the
|
|
5346
|
-
* virtualized shell folds each side of the recap boundary separately, and the
|
|
5347
|
-
* rows still have to say where they sit for the catch-up dimming.
|
|
5348
|
-
* @param fold Whether to group at all. `false` gives one block per item,
|
|
5349
|
-
* which is what the cards variant renders: this is the terminal theme's rule
|
|
5350
|
-
* and must not silently reshape another renderer's row list.
|
|
5351
|
-
*/
|
|
5352
|
-
function terminalBlocks(items, offset = 0, fold = true) {
|
|
5353
|
-
const out = [];
|
|
5354
|
-
for (const [position, item] of items.entries()) {
|
|
5355
|
-
const index = offset + position;
|
|
5356
|
-
const previous = out.at(-1);
|
|
5357
|
-
if (fold && isRunCall(item)) {
|
|
5358
|
-
if (previous && "run" in previous && foldsTogether(previous.run[0], item)) previous.run.push(item);
|
|
5359
|
-
else out.push({
|
|
5360
|
-
key: `run:${item.id}`,
|
|
5361
|
-
run: [item],
|
|
5362
|
-
index
|
|
5363
|
-
});
|
|
5364
|
-
continue;
|
|
5365
|
-
}
|
|
5366
|
-
out.push({
|
|
5367
|
-
key: `${item.kind}:${item.id}`,
|
|
5368
|
-
item,
|
|
5369
|
-
index
|
|
5370
|
-
});
|
|
5371
|
-
}
|
|
5372
|
-
return out;
|
|
5373
|
-
}
|
|
5374
5909
|
function TurnResultRow$1({ item }) {
|
|
5375
5910
|
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs(Row, {
|
|
5376
5911
|
tone: item.isError ? "red" : "faint",
|
|
@@ -5456,19 +5991,6 @@ function WorkingRow({ label, startedAt, tokens }) {
|
|
|
5456
5991
|
}) : null]
|
|
5457
5992
|
});
|
|
5458
5993
|
}
|
|
5459
|
-
/** Spacing between two items: a blank line, unless the pair belongs together.
|
|
5460
|
-
* Tool output already sits under its call, and a run of tool calls reads as one
|
|
5461
|
-
* block — the CLI leaves no blank line inside either. */
|
|
5462
|
-
function needsBlank(previous, next) {
|
|
5463
|
-
if (previous.kind === "tool_call" && next.kind === "tool_call") return false;
|
|
5464
|
-
return true;
|
|
5465
|
-
}
|
|
5466
|
-
/** The same rule over blocks: a shell run counts as the tool calls it folded. */
|
|
5467
|
-
function blockNeedsBlank(previous, next) {
|
|
5468
|
-
const before = "run" in previous ? "tool_call" : previous.item.kind;
|
|
5469
|
-
const after = "run" in next ? "tool_call" : next.item.kind;
|
|
5470
|
-
return !(before === "tool_call" && after === "tool_call");
|
|
5471
|
-
}
|
|
5472
5994
|
//#endregion
|
|
5473
5995
|
//#region src/components/terminal/surface.tsx
|
|
5474
5996
|
function TerminalSurface({ fontSize, lineHeight, bleed, affordances, className, style, children, ...props }) {
|
|
@@ -6946,6 +7468,101 @@ function PermissionPrompt({ request, onApprove, onDeny, className }) {
|
|
|
6946
7468
|
});
|
|
6947
7469
|
}
|
|
6948
7470
|
//#endregion
|
|
7471
|
+
//#region src/components/agent/SubagentStrip.tsx
|
|
7472
|
+
/**
|
|
7473
|
+
* The one line above a sub-agent takeover: who this is, how it is doing, and the
|
|
7474
|
+
* way back.
|
|
7475
|
+
*
|
|
7476
|
+
* **It claims exactly what the `TaskRow` it was opened from claims** — the same
|
|
7477
|
+
* `taskBusy` / `taskFailed` / tool count over the same items — and deliberately
|
|
7478
|
+
* *not* `SubagentInfo.status`. Protocol documents those two as divergent on
|
|
7479
|
+
* purpose (`index.ts:1476-1487`), but that divergence is transcript-versus-*list*
|
|
7480
|
+
* and this surface **is** the transcript. The disagreement that must not exist
|
|
7481
|
+
* here is between a header and the rows directly beneath it.
|
|
7482
|
+
*
|
|
7483
|
+
* The rollup is still allowed one job — naming an agent whose `Task` call is not
|
|
7484
|
+
* in the transcript — because a label is not content, and `SessionInfo.subagents`
|
|
7485
|
+
* keeps only eight settled records (`SUBAGENT_HISTORY`), so it can never be the
|
|
7486
|
+
* source of what a frame *shows*.
|
|
7487
|
+
*
|
|
7488
|
+
* Both variants, because the panel has two and a strip that only existed in one
|
|
7489
|
+
* would make the takeover a terminal-theme feature rather than a session feature.
|
|
7490
|
+
*/
|
|
7491
|
+
function SubagentStrip({ task, items, label, onBack, terminal, fontSize, lineHeight }) {
|
|
7492
|
+
const busy = task ? taskBusy(task, items) : false;
|
|
7493
|
+
const failed = task ? taskFailed(task) : false;
|
|
7494
|
+
const pulse = usePulse(busy);
|
|
7495
|
+
const tools = items.reduce((n, item) => n + (item.kind === "tool_call" ? 1 : 0), 0);
|
|
7496
|
+
const startedAt = busy ? task?.ts : void 0;
|
|
7497
|
+
const now = useTicker(startedAt !== void 0);
|
|
7498
|
+
const elapsed = startedAt === void 0 ? void 0 : formatDuration(now - startedAt);
|
|
7499
|
+
const name = task ? taskIdentity(task) : label;
|
|
7500
|
+
/**
|
|
7501
|
+
* Running or finished, in the theme's own words rather than new ones:
|
|
7502
|
+
* `taskSummary` already says `working…` and `done` for exactly this state one
|
|
7503
|
+
* row over, and a header that said "running"/"completed" about the same agent
|
|
7504
|
+
* would be a second vocabulary for one fact. The trailing ellipsis is the
|
|
7505
|
+
* theme's in-flight signal; the pulse beside it is the beat.
|
|
7506
|
+
*
|
|
7507
|
+
* Unknown when the transcript has no `Task` call to read — better silent than
|
|
7508
|
+
* confidently wrong about an agent we cannot see.
|
|
7509
|
+
*/
|
|
7510
|
+
const status = !task ? void 0 : failed ? "failed" : busy ? `${pulse} working…` : "done";
|
|
7511
|
+
const detail = [tools > 0 ? `${tools} tool${tools === 1 ? "" : "s"}` : void 0, elapsed].filter(Boolean).join(" · ");
|
|
7512
|
+
if (terminal) return /* @__PURE__ */ jsx(TerminalSurface, {
|
|
7513
|
+
fontSize,
|
|
7514
|
+
lineHeight,
|
|
7515
|
+
className: "shrink-0",
|
|
7516
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
7517
|
+
type: "button",
|
|
7518
|
+
onClick: onBack,
|
|
7519
|
+
"aria-label": "Back to the session",
|
|
7520
|
+
className: "block w-full cursor-pointer text-left",
|
|
7521
|
+
children: /* @__PURE__ */ jsxs(Row, {
|
|
7522
|
+
glyph: "←",
|
|
7523
|
+
glyphTone: "dim",
|
|
7524
|
+
indent: 1,
|
|
7525
|
+
tone: failed ? "red" : "green",
|
|
7526
|
+
children: [
|
|
7527
|
+
name,
|
|
7528
|
+
status ? /* @__PURE__ */ jsxs(Ink, {
|
|
7529
|
+
tone: failed ? "red" : busy ? "mark" : "dim",
|
|
7530
|
+
children: [" · ", status]
|
|
7531
|
+
}) : null,
|
|
7532
|
+
detail ? /* @__PURE__ */ jsxs(Ink, {
|
|
7533
|
+
tone: "faint",
|
|
7534
|
+
children: [" · ", detail]
|
|
7535
|
+
}) : null
|
|
7536
|
+
]
|
|
7537
|
+
})
|
|
7538
|
+
})
|
|
7539
|
+
});
|
|
7540
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
7541
|
+
className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-1.5",
|
|
7542
|
+
children: [
|
|
7543
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
7544
|
+
variant: "ghost",
|
|
7545
|
+
size: "sm",
|
|
7546
|
+
onClick: onBack,
|
|
7547
|
+
className: "h-6 gap-1 px-1.5",
|
|
7548
|
+
children: [/* @__PURE__ */ jsx(ArrowLeft, { className: "size-3.5" }), "Back"]
|
|
7549
|
+
}),
|
|
7550
|
+
/* @__PURE__ */ jsx("span", {
|
|
7551
|
+
className: cn("min-w-0 flex-1 truncate text-body-sm", failed ? "text-danger" : "text-fg-2"),
|
|
7552
|
+
children: name
|
|
7553
|
+
}),
|
|
7554
|
+
status ? /* @__PURE__ */ jsx("span", {
|
|
7555
|
+
className: cn("shrink-0 text-label", failed ? "text-danger" : busy ? "text-accent" : "text-fg-3"),
|
|
7556
|
+
children: status
|
|
7557
|
+
}) : null,
|
|
7558
|
+
detail ? /* @__PURE__ */ jsx("span", {
|
|
7559
|
+
className: "shrink-0 text-label text-fg-4",
|
|
7560
|
+
children: detail
|
|
7561
|
+
}) : null
|
|
7562
|
+
]
|
|
7563
|
+
});
|
|
7564
|
+
}
|
|
7565
|
+
//#endregion
|
|
6949
7566
|
//#region src/components/agent/QuestionPrompt.tsx
|
|
6950
7567
|
/** The AskUserQuestion policies surfaced on job/session creation forms. */
|
|
6951
7568
|
const QUESTION_BEHAVIORS = [
|
|
@@ -7145,7 +7762,7 @@ function Hint({ children }) {
|
|
|
7145
7762
|
* background gradients rather than a border, so it costs no layout: a 1px border
|
|
7146
7763
|
* would push its contents a pixel off the column every other row sits on.
|
|
7147
7764
|
*/
|
|
7148
|
-
function Box({ children, className }) {
|
|
7765
|
+
function Box$1({ children, className }) {
|
|
7149
7766
|
return /* @__PURE__ */ jsx("div", {
|
|
7150
7767
|
className: cn("term-box", className),
|
|
7151
7768
|
children
|
|
@@ -7522,7 +8139,7 @@ function QuestionStep({ question, selection, cursor, onCursor, onToggle, onOther
|
|
|
7522
8139
|
checked: multiSelect ? selected : void 0,
|
|
7523
8140
|
marker: "check",
|
|
7524
8141
|
selected: !multiSelect && selected,
|
|
7525
|
-
detail: option.preview && cursor === index ? /* @__PURE__ */ jsx(Box, { children: option.preview.split("\n").map((line, row) => /* @__PURE__ */ jsx(Row, {
|
|
8142
|
+
detail: option.preview && cursor === index ? /* @__PURE__ */ jsx(Box$1, { children: option.preview.split("\n").map((line, row) => /* @__PURE__ */ jsx(Row, {
|
|
7526
8143
|
columns: 0,
|
|
7527
8144
|
tone: "dim",
|
|
7528
8145
|
children: line || " "
|
|
@@ -7842,7 +8459,9 @@ function useNow(intervalMs = 3e4) {
|
|
|
7842
8459
|
}, [intervalMs]);
|
|
7843
8460
|
return now;
|
|
7844
8461
|
}
|
|
7845
|
-
|
|
8462
|
+
/** The shared ramp, aliased for the two call sites below — the thresholds live
|
|
8463
|
+
* in `lib/status.ts` beside the severity they mirror. */
|
|
8464
|
+
const utilizationColor = (pct) => meterColorClass(pct);
|
|
7846
8465
|
/** The CLI reports category colors as its own theme token names ('inactive',
|
|
7847
8466
|
* 'promptBorder', ...), not CSS colors — only pass through what CSS can render. */
|
|
7848
8467
|
const cssColor = (color) => typeof CSS !== "undefined" && CSS.supports("color", color) ? color : void 0;
|
|
@@ -8387,14 +9006,18 @@ function resultLanguage(item) {
|
|
|
8387
9006
|
function ToolCallCard({ item, hostImage, className }) {
|
|
8388
9007
|
const [open, setOpen] = useState(false);
|
|
8389
9008
|
const [fullResult, setFullResult] = useState(false);
|
|
9009
|
+
const [fetching, setFetching] = useState(false);
|
|
9010
|
+
const fetchResult = useToolResultFetcher();
|
|
8390
9011
|
const imagePath = imagePathOf(item);
|
|
8391
9012
|
const status = item.status ?? (item.result === void 0 ? "running" : "settled");
|
|
8392
9013
|
const badge = STATE_BADGE[status];
|
|
8393
9014
|
const isError = status === "failed" || item.result?.isError === true;
|
|
8394
9015
|
const Icon = toolIcon(item.name);
|
|
8395
9016
|
const resultText = item.result?.text ?? "";
|
|
8396
|
-
const
|
|
8397
|
-
const shownResult =
|
|
9017
|
+
const clipped = !fullResult && resultText.length > RESULT_PREVIEW_CHARS;
|
|
9018
|
+
const shownResult = clipped ? resultText.slice(0, RESULT_PREVIEW_CHARS) : resultText;
|
|
9019
|
+
const headOnly = item.result?.truncated === true;
|
|
9020
|
+
const totalChars = item.result?.totalChars ?? resultText.length;
|
|
8398
9021
|
const details = open ? /* @__PURE__ */ jsxs("div", {
|
|
8399
9022
|
className: "flex flex-col gap-2 border-t border-border p-2.5",
|
|
8400
9023
|
children: [
|
|
@@ -8412,13 +9035,25 @@ function ToolCallCard({ item, hostImage, className }) {
|
|
|
8412
9035
|
language: resultLanguage(item),
|
|
8413
9036
|
label: isError ? "Error" : "Result",
|
|
8414
9037
|
className: cn(isError && "border-danger/40 [&_pre]:text-danger")
|
|
8415
|
-
}),
|
|
9038
|
+
}), fetching ? /* @__PURE__ */ jsxs("p", {
|
|
9039
|
+
className: "mt-1 text-label text-fg-3",
|
|
9040
|
+
children: [
|
|
9041
|
+
"Fetching ",
|
|
9042
|
+
totalChars.toLocaleString(),
|
|
9043
|
+
" chars…"
|
|
9044
|
+
]
|
|
9045
|
+
}) : clipped || headOnly ? /* @__PURE__ */ jsxs("button", {
|
|
8416
9046
|
type: "button",
|
|
8417
9047
|
className: "mt-1 text-label text-fg-3 underline-offset-2 hover:underline",
|
|
8418
|
-
onClick: () =>
|
|
9048
|
+
onClick: () => {
|
|
9049
|
+
setFullResult(true);
|
|
9050
|
+
if (!headOnly) return;
|
|
9051
|
+
setFetching(true);
|
|
9052
|
+
fetchResult(item.id).finally(() => setFetching(false));
|
|
9053
|
+
},
|
|
8419
9054
|
children: [
|
|
8420
9055
|
"Show all ",
|
|
8421
|
-
|
|
9056
|
+
totalChars.toLocaleString(),
|
|
8422
9057
|
" chars"
|
|
8423
9058
|
]
|
|
8424
9059
|
}) : null] }) : null
|
|
@@ -8428,6 +9063,13 @@ function ToolCallCard({ item, hostImage, className }) {
|
|
|
8428
9063
|
path: imagePath,
|
|
8429
9064
|
load: hostImage
|
|
8430
9065
|
}) : null;
|
|
9066
|
+
const resultImages = item.result?.images?.length ? /* @__PURE__ */ jsx("div", {
|
|
9067
|
+
className: "flex flex-col gap-2 border-t border-border p-2.5",
|
|
9068
|
+
children: item.result.images.map((ref) => /* @__PURE__ */ jsx(ResultImage, {
|
|
9069
|
+
toolUseId: item.id,
|
|
9070
|
+
image: ref
|
|
9071
|
+
}, ref.partIndex))
|
|
9072
|
+
}) : null;
|
|
8431
9073
|
return /* @__PURE__ */ jsxs("div", {
|
|
8432
9074
|
"data-slot": "tool-call",
|
|
8433
9075
|
"data-state": status,
|
|
@@ -8466,6 +9108,7 @@ function ToolCallCard({ item, hostImage, className }) {
|
|
|
8466
9108
|
]
|
|
8467
9109
|
}),
|
|
8468
9110
|
image,
|
|
9111
|
+
resultImages,
|
|
8469
9112
|
details
|
|
8470
9113
|
]
|
|
8471
9114
|
});
|
|
@@ -8481,6 +9124,34 @@ function PlainPayload({ code, label, className }) {
|
|
|
8481
9124
|
});
|
|
8482
9125
|
}
|
|
8483
9126
|
/**
|
|
9127
|
+
* An image part of the result, fetched by reference.
|
|
9128
|
+
*
|
|
9129
|
+
* The frame is a **fixed height in all three states** — placeholder, picture,
|
|
9130
|
+
* failure — which is the one rule this shares with the terminal theme and the
|
|
9131
|
+
* only reason it is worth a component: the transcript is virtualized in both,
|
|
9132
|
+
* and a box that appears when the bytes land shoves every row below it down
|
|
9133
|
+
* while the reader is mid-sentence. Unlike `HostImage`, a failure here is *said*
|
|
9134
|
+
* rather than swallowed: there is no host path in the result text to fall back
|
|
9135
|
+
* on, so silence would be a blank frame with no account of itself.
|
|
9136
|
+
*/
|
|
9137
|
+
function ResultImage({ toolUseId, image }) {
|
|
9138
|
+
const { src, failed } = useToolResultImageSrc({
|
|
9139
|
+
toolUseId,
|
|
9140
|
+
...image
|
|
9141
|
+
});
|
|
9142
|
+
return /* @__PURE__ */ jsx("div", {
|
|
9143
|
+
className: "flex h-60 items-start overflow-hidden rounded-md border border-border bg-surface-hover",
|
|
9144
|
+
children: src ? /* @__PURE__ */ jsx("img", {
|
|
9145
|
+
src,
|
|
9146
|
+
alt: imagePlaceholder(image),
|
|
9147
|
+
className: "h-full max-w-full object-contain"
|
|
9148
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
9149
|
+
className: "p-2 text-label text-fg-4",
|
|
9150
|
+
children: failed ? IMAGE_UNAVAILABLE : imagePlaceholder(image)
|
|
9151
|
+
})
|
|
9152
|
+
});
|
|
9153
|
+
}
|
|
9154
|
+
/**
|
|
8484
9155
|
* A picture that lives on the host, fetched through the gateway's host-file
|
|
8485
9156
|
* route and shown inline.
|
|
8486
9157
|
*
|
|
@@ -8525,13 +9196,26 @@ function createHeightEpoch(width, ch, line) {
|
|
|
8525
9196
|
* The inter-row gap is the *pair's* business (`gapBefore`), not the row's, so
|
|
8526
9197
|
* it is added by the caller. */
|
|
8527
9198
|
function estimateBlockPx(block, epoch) {
|
|
8528
|
-
if ("
|
|
9199
|
+
if (!("item" in block)) return blockHeight(block, epoch).px;
|
|
8529
9200
|
const hit = epoch.cache.get(block.item);
|
|
8530
9201
|
if (hit) return hit.px;
|
|
8531
9202
|
const computed = itemHeight(block.item, epoch);
|
|
8532
9203
|
epoch.cache.set(block.item, computed);
|
|
8533
9204
|
return computed.px;
|
|
8534
9205
|
}
|
|
9206
|
+
/**
|
|
9207
|
+
* The collapsed brief row's height — the frame's first row, which is synthetic
|
|
9208
|
+
* and so cannot go through {@link estimateBlockPx}.
|
|
9209
|
+
*
|
|
9210
|
+
* Clipped, so it is `min(wrapped, BRIEF_LINES)` lines plus the row the header
|
|
9211
|
+
* occupies. Expanding is local state on a *mounted* row, which the virtualizer
|
|
9212
|
+
* re-measures; what has to be right before it mounts is the collapsed height,
|
|
9213
|
+
* exactly as a task row is always collapsed when unmounted for the same reason.
|
|
9214
|
+
*/
|
|
9215
|
+
function briefPx(text, m) {
|
|
9216
|
+
const cols = Math.max(1, Math.floor(m.width / m.ch + EPS));
|
|
9217
|
+
return (Math.min(textLines(text, cols).lines, 4) + 1) * m.line;
|
|
9218
|
+
}
|
|
8535
9219
|
/** Measure the advance of `0` on the live surface. A DOM read — call it from
|
|
8536
9220
|
* the epoch's measurement pass (an effect / ResizeObserver callback), never
|
|
8537
9221
|
* from render. The probe is absolutely positioned, so it contributes no layout
|
|
@@ -9089,10 +9773,15 @@ function toolRowHeight(item, m, extraPx) {
|
|
|
9089
9773
|
gutterCells: 2,
|
|
9090
9774
|
extraPx
|
|
9091
9775
|
});
|
|
9776
|
+
const images = item.result?.images;
|
|
9777
|
+
if (images?.length) acc = add(acc, {
|
|
9778
|
+
px: images.length * 12 * m.line,
|
|
9779
|
+
exact: true
|
|
9780
|
+
});
|
|
9092
9781
|
if (item.patch) return add(acc, diffHeight(item.patch, m, extraPx));
|
|
9093
9782
|
const text = item.result?.text ?? "";
|
|
9094
9783
|
if (!text) return acc;
|
|
9095
|
-
const { shown, more } = collapsedResult(text.trimEnd().split("\n"));
|
|
9784
|
+
const { shown, more } = collapsedResult(text.trimEnd().split("\n"), item.result?.totalChars);
|
|
9096
9785
|
for (const line of shown) acc = add(acc, rowH(line || " ", m, {
|
|
9097
9786
|
indentCells: 3,
|
|
9098
9787
|
gutterCells: 3,
|
|
@@ -9138,9 +9827,14 @@ function itemHeight(item, m) {
|
|
|
9138
9827
|
};
|
|
9139
9828
|
}
|
|
9140
9829
|
}
|
|
9141
|
-
/** A virtual row's height: an item,
|
|
9142
|
-
* summary line, built by the same function
|
|
9830
|
+
/** A virtual row's height: an item, a folded tool run, or a task block —
|
|
9831
|
+
* either fold collapsed is its one summary line, built by the same function
|
|
9832
|
+
* the row draws (`runSummary` / `taskSummary`), rendered as one standard
|
|
9833
|
+
* 2-cell-gutter `Row`. No expanded branch for the task block either: it is
|
|
9834
|
+
* always collapsed by default, which is the invariant that keeps every
|
|
9835
|
+
* unmounted row's estimate exact. */
|
|
9143
9836
|
function blockHeight(block, m) {
|
|
9837
|
+
if ("task" in block) return rowH(taskSummary(block.task, taskChildItems(block)), m);
|
|
9144
9838
|
if ("run" in block) {
|
|
9145
9839
|
const busy = block.run.some((item) => item.status === "running" || item.status === "pending");
|
|
9146
9840
|
return rowH(runSummary(block.run, busy), m);
|
|
@@ -9157,22 +9851,27 @@ function nearestMember(cluster, y) {
|
|
|
9157
9851
|
return best?.mark;
|
|
9158
9852
|
}
|
|
9159
9853
|
/**
|
|
9160
|
-
*
|
|
9161
|
-
*
|
|
9162
|
-
*
|
|
9163
|
-
* a
|
|
9164
|
-
*
|
|
9165
|
-
*
|
|
9854
|
+
* The two lanes are **channels, not classes**: left is what went *in* — your
|
|
9855
|
+
* prompts, and the sub-agents you dispatched — and right is what came *out* —
|
|
9856
|
+
* each turn's answer, and everything that went wrong producing one. That is the
|
|
9857
|
+
* question a reader actually asks of a rail ("where did I say something", "where
|
|
9858
|
+
* did it go wrong"), and it puts every failure in one column instead of
|
|
9859
|
+
* scattering some down the middle.
|
|
9860
|
+
*
|
|
9861
|
+
* Full width is reserved for what is not a channel at all: a waiting approval
|
|
9862
|
+
* (which is the session asking *you*, pinned at the foot), a bookmark (the
|
|
9863
|
+
* reader's own annotation) and the catch-up seam (a boundary across both).
|
|
9166
9864
|
*
|
|
9167
9865
|
* It also buys the marks their width back: three lanes in a 16px rail is 5px a
|
|
9168
9866
|
* lane, which is a hard target to hit and a hard colour to see.
|
|
9169
9867
|
*/
|
|
9170
9868
|
const LANE = {
|
|
9171
9869
|
user: "l",
|
|
9870
|
+
subagent: "l",
|
|
9172
9871
|
turn: "r",
|
|
9173
9872
|
turnFailed: "r",
|
|
9174
|
-
toolFailed: "
|
|
9175
|
-
error: "
|
|
9873
|
+
toolFailed: "r",
|
|
9874
|
+
error: "r",
|
|
9176
9875
|
approval: "f",
|
|
9177
9876
|
recap: "f",
|
|
9178
9877
|
bookmark: "f"
|
|
@@ -9186,10 +9885,12 @@ const LOUDNESS = {
|
|
|
9186
9885
|
user: 3,
|
|
9187
9886
|
turn: 2,
|
|
9188
9887
|
bookmark: 1,
|
|
9888
|
+
subagent: 1,
|
|
9189
9889
|
recap: 0
|
|
9190
9890
|
};
|
|
9191
9891
|
const KIND_NAME = {
|
|
9192
9892
|
user: "you",
|
|
9893
|
+
subagent: "sub-agent",
|
|
9193
9894
|
turn: "response · turn end",
|
|
9194
9895
|
turnFailed: "turn failed",
|
|
9195
9896
|
toolFailed: "tool failed",
|
|
@@ -9235,9 +9936,27 @@ function excerpt(item) {
|
|
|
9235
9936
|
default: return "";
|
|
9236
9937
|
}
|
|
9237
9938
|
}
|
|
9939
|
+
/**
|
|
9940
|
+
* Exported for `test/scrubber.test.ts` and nothing else — it is not part of the
|
|
9941
|
+
* package's surface (`index.ts` does not re-export it). Both of the bugs this
|
|
9942
|
+
* function has shipped were pure-logic ones a unit test catches: a live answer
|
|
9943
|
+
* with no `turn_result` yet went unmarked for the whole two minutes it was the
|
|
9944
|
+
* only thing worth navigating to, and a replayed history — which carries no turn
|
|
9945
|
+
* rows at all — came back with an empty right lane.
|
|
9946
|
+
*/
|
|
9238
9947
|
function buildClusters(props, railH) {
|
|
9239
|
-
const { items, bookmarks, recapRow, pendingApprovals, rowIndexFor, offsetOfRow, sizeOfRow, totalSize, viewportH } = props;
|
|
9948
|
+
const { items, bookmarks, frameParentId, recapRow, pendingApprovals, rowIndexFor, offsetOfRow, sizeOfRow, positionInRow, totalSize, viewportH } = props;
|
|
9240
9949
|
const marks = [];
|
|
9950
|
+
const subagentParents = /* @__PURE__ */ new Set();
|
|
9951
|
+
for (const item of items) {
|
|
9952
|
+
const parent = parentOf(item);
|
|
9953
|
+
if (parent !== void 0) subagentParents.add(parent);
|
|
9954
|
+
}
|
|
9955
|
+
const rowOutcome = /* @__PURE__ */ new Map();
|
|
9956
|
+
items.forEach((item, index) => {
|
|
9957
|
+
if (item.kind !== "tool_call" || parentOf(item) !== frameParentId) return;
|
|
9958
|
+
rowOutcome.set(rowIndexFor(index), index);
|
|
9959
|
+
});
|
|
9241
9960
|
let segment = {};
|
|
9242
9961
|
const closeSegment = () => {
|
|
9243
9962
|
const anchor = segment.response ?? segment.turn;
|
|
@@ -9250,7 +9969,12 @@ function buildClusters(props, railH) {
|
|
|
9250
9969
|
segment = {};
|
|
9251
9970
|
};
|
|
9252
9971
|
items.forEach((item, index) => {
|
|
9253
|
-
if (item.kind === "
|
|
9972
|
+
if (item.kind === "tool_call" && subagentParents.has(item.id)) marks.push({
|
|
9973
|
+
kind: "subagent",
|
|
9974
|
+
itemIndex: index,
|
|
9975
|
+
rowIndex: rowIndexFor(index)
|
|
9976
|
+
});
|
|
9977
|
+
if (item.kind === "user" && parentOf(item) === frameParentId) {
|
|
9254
9978
|
closeSegment();
|
|
9255
9979
|
marks.push({
|
|
9256
9980
|
kind: "user",
|
|
@@ -9266,12 +9990,22 @@ function buildClusters(props, railH) {
|
|
|
9266
9990
|
itemIndex: index,
|
|
9267
9991
|
rowIndex: rowIndexFor(index)
|
|
9268
9992
|
});
|
|
9269
|
-
else if (item.kind === "tool_call" && (item.status === "failed" || item.result?.isError === true)) marks.push({
|
|
9993
|
+
else if (item.kind === "tool_call" && (item.status === "failed" || item.result?.isError === true) && rowOutcome.get(rowIndexFor(index)) === index) marks.push({
|
|
9270
9994
|
kind: "toolFailed",
|
|
9271
9995
|
itemIndex: index,
|
|
9272
9996
|
rowIndex: rowIndexFor(index)
|
|
9273
9997
|
});
|
|
9274
|
-
else if (item.kind === "assistant_text" && item
|
|
9998
|
+
else if (item.kind === "assistant_text" && parentOf(item) === frameParentId) {
|
|
9999
|
+
if (frameParentId !== void 0) {
|
|
10000
|
+
marks.push({
|
|
10001
|
+
kind: "turn",
|
|
10002
|
+
itemIndex: index,
|
|
10003
|
+
rowIndex: rowIndexFor(index)
|
|
10004
|
+
});
|
|
10005
|
+
return;
|
|
10006
|
+
}
|
|
10007
|
+
segment.response = index;
|
|
10008
|
+
}
|
|
9275
10009
|
});
|
|
9276
10010
|
closeSegment();
|
|
9277
10011
|
for (const index of bookmarks) if (index >= 0 && index < items.length) marks.push({
|
|
@@ -9287,8 +10021,10 @@ function buildClusters(props, railH) {
|
|
|
9287
10021
|
const scale = railScale(railH, totalSize, viewportH);
|
|
9288
10022
|
const lanes = /* @__PURE__ */ new Map();
|
|
9289
10023
|
for (const mark of marks) {
|
|
9290
|
-
const
|
|
9291
|
-
const
|
|
10024
|
+
const within = mark.itemIndex >= 0 ? positionInRow?.(mark.itemIndex) : void 0;
|
|
10025
|
+
const rowH = sizeOfRow(mark.rowIndex);
|
|
10026
|
+
const h = within ? MIN_MARK : Math.max(MIN_MARK, Math.round(rowH * scale));
|
|
10027
|
+
const y = Math.min(Math.max(0, railH - h), Math.round((offsetOfRow(mark.rowIndex) + (within ? within.ordinal / within.count * rowH : 0)) * scale));
|
|
9292
10028
|
const lane = LANE[mark.kind];
|
|
9293
10029
|
const list = lanes.get(lane) ?? [];
|
|
9294
10030
|
list.push({
|
|
@@ -9560,12 +10296,14 @@ function TerminalScrubber(props) {
|
|
|
9560
10296
|
}
|
|
9561
10297
|
//#endregion
|
|
9562
10298
|
//#region src/components/agent/transcript-rows.ts
|
|
9563
|
-
/** The item a row is spaced *as*. A run stands for the calls it folded,
|
|
9564
|
-
*
|
|
10299
|
+
/** The item a row is spaced *as*. A run stands for the calls it folded, and a
|
|
10300
|
+
* task block for the `Task` call it absorbed into — all tool calls, so a run,
|
|
10301
|
+
* a task and a lone tool call below them still read as one block. */
|
|
9565
10302
|
function rowItem(row) {
|
|
9566
10303
|
if (!row) return void 0;
|
|
9567
10304
|
if ("item" in row) return row.item;
|
|
9568
10305
|
if ("run" in row) return row.run[0];
|
|
10306
|
+
if ("task" in row) return row.task;
|
|
9569
10307
|
}
|
|
9570
10308
|
/**
|
|
9571
10309
|
* Does a blank line go above this row, in the terminal theme?
|
|
@@ -9581,26 +10319,63 @@ function gapBefore(rows, index) {
|
|
|
9581
10319
|
return needsBlank(before, after);
|
|
9582
10320
|
}
|
|
9583
10321
|
/**
|
|
10322
|
+
* Which items each task block absorbed, as itemIndex → rowIndex — the one
|
|
10323
|
+
* lookup {@link rowIndexForItem} cannot answer from ordering (see its comment).
|
|
10324
|
+
* Memoized per rows array identity: the shell builds `rows` in a `useMemo`, so
|
|
10325
|
+
* within one row list this is built once, and a WeakMap means a discarded list
|
|
10326
|
+
* takes its map with it. Memoization only — the answer is a pure function of
|
|
10327
|
+
* the array.
|
|
10328
|
+
*/
|
|
10329
|
+
const absorbedCache = /* @__PURE__ */ new WeakMap();
|
|
10330
|
+
function absorbedRows(rows) {
|
|
10331
|
+
const hit = absorbedCache.get(rows);
|
|
10332
|
+
if (hit) return hit;
|
|
10333
|
+
const map = /* @__PURE__ */ new Map();
|
|
10334
|
+
rows.forEach((row, rowIndex) => {
|
|
10335
|
+
if ("task" in row) for (const itemIndex of row.childIndices) map.set(itemIndex, rowIndex);
|
|
10336
|
+
});
|
|
10337
|
+
absorbedCache.set(rows, map);
|
|
10338
|
+
return map;
|
|
10339
|
+
}
|
|
10340
|
+
/**
|
|
9584
10341
|
* Transcript-item index → virtual-row index — **the off-by-a-fold trap.**
|
|
9585
10342
|
*
|
|
9586
10343
|
* The virtualizer's rows are {@link TerminalBlock}s, not items: a folded tool
|
|
9587
|
-
* run occupies ONE row for `run.length` consecutive items,
|
|
9588
|
-
*
|
|
9589
|
-
*
|
|
9590
|
-
*
|
|
9591
|
-
*
|
|
9592
|
-
*
|
|
9593
|
-
*
|
|
9594
|
-
*
|
|
9595
|
-
*
|
|
9596
|
-
*
|
|
9597
|
-
*
|
|
9598
|
-
*
|
|
9599
|
-
*
|
|
9600
|
-
*
|
|
9601
|
-
*
|
|
10344
|
+
* run occupies ONE row for `run.length` consecutive items, a task block
|
|
10345
|
+
* occupies ONE row for its `Task` call *plus every item its subagent produced*,
|
|
10346
|
+
* and the recap boundary is a row with *no* item index at all, shifting every
|
|
10347
|
+
* row after it by one. `virtualizer.scrollToIndex(itemIndex)` is therefore
|
|
10348
|
+
* wrong by construction on any folded or spliced transcript — every jump that
|
|
10349
|
+
* starts from an item (the scrubber's marks, a future bookmark) must come
|
|
10350
|
+
* through here first.
|
|
10351
|
+
*
|
|
10352
|
+
* The contract, in two halves:
|
|
10353
|
+
*
|
|
10354
|
+
* - An index a task block **absorbed** maps to that block's row, wherever the
|
|
10355
|
+
* child fell in the stream. Subagents run in parallel, so absorbed indices
|
|
10356
|
+
* interleave arbitrarily with later rows' starts — no ordering argument can
|
|
10357
|
+
* find their row, which is why they are answered first, from a per-row-list
|
|
10358
|
+
* map ({@link absorbedRows}) built once per rows array. A row's coverage is
|
|
10359
|
+
* its `childIndices`, never `[index, index + N)` arithmetic.
|
|
10360
|
+
* - Every other index maps to the **last non-recap row whose start (`index`)
|
|
10361
|
+
* is ≤ the target** — the original rule, still a binary search. Rows stay
|
|
10362
|
+
* ordered by `index`, and the ordering argument is now: between one row's
|
|
10363
|
+
* start and the next row's, every index is either absorbed (answered above)
|
|
10364
|
+
* or a member of the earlier row — note that is *weaker* than the old
|
|
10365
|
+
* contiguity claim, because a run can fold across an absorbed gap (two
|
|
10366
|
+
* top-level calls separated only by a subagent's step are adjacent on
|
|
10367
|
+
* screen), so `[index, index + run.length)` arithmetic no longer describes
|
|
10368
|
+
* a run's coverage; membership does. The recap row is skipped by giving it
|
|
10369
|
+
* its successor's start for navigation (both qualify at the boundary, and
|
|
10370
|
+
* "last wins" lands on the real row) while never letting it be the answer.
|
|
10371
|
+
*
|
|
10372
|
+
* Exhaustively checked against a linear reference — every fixture × every item
|
|
10373
|
+
* index × several splice positions — by `__wdCheckMapping` in `dev/App.tsx`,
|
|
10374
|
+
* and against constructed interleavings in `test/transcript-rows.test.ts`.
|
|
9602
10375
|
*/
|
|
9603
10376
|
function rowIndexForItem(rows, itemIndex) {
|
|
10377
|
+
const absorbed = absorbedRows(rows).get(itemIndex);
|
|
10378
|
+
if (absorbed !== void 0) return absorbed;
|
|
9604
10379
|
let lo = 0;
|
|
9605
10380
|
let hi = rows.length - 1;
|
|
9606
10381
|
let best = 0;
|
|
@@ -9620,6 +10395,49 @@ function rowIndexForItem(rows, itemIndex) {
|
|
|
9620
10395
|
}
|
|
9621
10396
|
return best;
|
|
9622
10397
|
}
|
|
10398
|
+
const positionCache = /* @__PURE__ */ new WeakMap();
|
|
10399
|
+
function rowPositions(rows) {
|
|
10400
|
+
const hit = positionCache.get(rows);
|
|
10401
|
+
if (hit) return hit;
|
|
10402
|
+
const map = /* @__PURE__ */ new Map();
|
|
10403
|
+
for (const row of rows) if ("task" in row) {
|
|
10404
|
+
const count = row.childIndices.length;
|
|
10405
|
+
row.childIndices.forEach((itemIndex, ordinal) => map.set(itemIndex, {
|
|
10406
|
+
ordinal,
|
|
10407
|
+
count
|
|
10408
|
+
}));
|
|
10409
|
+
} else if ("run" in row && row.run.length > 1) {
|
|
10410
|
+
const count = row.run.length;
|
|
10411
|
+
row.indices.forEach((itemIndex, ordinal) => map.set(itemIndex, {
|
|
10412
|
+
ordinal,
|
|
10413
|
+
count
|
|
10414
|
+
}));
|
|
10415
|
+
}
|
|
10416
|
+
positionCache.set(rows, map);
|
|
10417
|
+
return map;
|
|
10418
|
+
}
|
|
10419
|
+
/**
|
|
10420
|
+
* Where an item sits inside a row that holds MORE than itself — a task block's
|
|
10421
|
+
* absorbed child, or a member of a folded run of two or more. `undefined` for
|
|
10422
|
+
* everything else, including a row's own head item (the `Task` call, a run's
|
|
10423
|
+
* first member is *not* exempt) and a **singleton run**: there the row's extent
|
|
10424
|
+
* IS the item's, and a mark spanning it is honest.
|
|
10425
|
+
*
|
|
10426
|
+
* That carve-out is load-bearing rather than tidy: `pushLeaf` makes *every*
|
|
10427
|
+
* top-level tool call a `RunBlock`, usually of length 1, so without it every
|
|
10428
|
+
* ordinary failed call's scrubber mark would shrink from its row's extent to a
|
|
10429
|
+
* tick and the rail would stop reading as a map — a regression traded for a fix.
|
|
10430
|
+
*
|
|
10431
|
+
* The scrubber is the consumer: a mark for a shared-row item anchors at
|
|
10432
|
+
* `ordinal / count` of the row's *measured* height instead of inheriting an
|
|
10433
|
+
* extent that is mostly other items' work (one failed child of a hundred-call
|
|
10434
|
+
* task painted the whole expanded block red). Memoized per rows array identity
|
|
10435
|
+
* exactly like {@link absorbedRows}, and pure — the answer is a function of the
|
|
10436
|
+
* array alone, and a discarded array takes its map with it.
|
|
10437
|
+
*/
|
|
10438
|
+
function positionInRow(rows, itemIndex) {
|
|
10439
|
+
return rowPositions(rows).get(itemIndex);
|
|
10440
|
+
}
|
|
9623
10441
|
//#endregion
|
|
9624
10442
|
//#region src/components/agent/use-height-epoch.ts
|
|
9625
10443
|
/**
|
|
@@ -9788,6 +10606,106 @@ function TerminalItemView({ item, fileUrl }) {
|
|
|
9788
10606
|
default: return null;
|
|
9789
10607
|
}
|
|
9790
10608
|
}
|
|
10609
|
+
/**
|
|
10610
|
+
* A `Task` and everything the subagent it spawned produced, as one row.
|
|
10611
|
+
*
|
|
10612
|
+
* The same claim the tool-run fold makes, and a stronger one: a subagent is
|
|
10613
|
+
* *sixty* rows of somebody else's working — a brief, a dozen greps, its own
|
|
10614
|
+
* thinking — and none of it is what you came back to read. What you came back
|
|
10615
|
+
* to read is the report, and the report is the model's next sentence. So the
|
|
10616
|
+
* whole frame collapses to one line saying what was asked and how big the
|
|
10617
|
+
* answer was, and opens in full the moment it is the thing you want.
|
|
10618
|
+
*
|
|
10619
|
+
* **Always collapsed when unmounted**, and that is load-bearing rather than
|
|
10620
|
+
* tidy: `height.ts` predicts this row as exactly one wrapped `taskSummary`, and
|
|
10621
|
+
* expansion is component-local state that dies with the row. A row auto-opening
|
|
10622
|
+
* because its subagent happens to be running would make its own height
|
|
10623
|
+
* unpredictable — which is why the live signal is *in* the collapsed line (the
|
|
10624
|
+
* pulse, and a count that climbs) rather than in an open block.
|
|
10625
|
+
*
|
|
10626
|
+
* The children are the theme's ordinary rows, stepped in behind a rule, and
|
|
10627
|
+
* they fold among themselves: a subagent's consecutive tool calls are as much
|
|
10628
|
+
* an aside inside its frame as they are in the main thread.
|
|
10629
|
+
*/
|
|
10630
|
+
/**
|
|
10631
|
+
* **What the agent was asked** — the sub-agent's brief, clipped to
|
|
10632
|
+
* {@link BRIEF_LINES} and pressable for the whole of it.
|
|
10633
|
+
*
|
|
10634
|
+
* It leads the takeover's frame and the inline task expansion alike, because
|
|
10635
|
+
* both are answering the same question and an answer without its question is
|
|
10636
|
+
* half a transcript. This is the one row here built from something other than
|
|
10637
|
+
* the stream, and it exists for the case the stream does not cover: a
|
|
10638
|
+
* **background** agent forwards no brief, where a foreground `Task` forwards a
|
|
10639
|
+
* real nested user item that renders as an ordinary prompt row. The callers
|
|
10640
|
+
* splice this in only when that row is absent. Codex draws nothing either way —
|
|
10641
|
+
* its spawn message is encrypted on the wire, so there is no brief to give.
|
|
10642
|
+
*
|
|
10643
|
+
* `>` and blue, the prompt's own marker and colour, because that is what this
|
|
10644
|
+
* is: somebody's instruction, one level in. The clip is `line-clamp`, which
|
|
10645
|
+
* cuts on the same wrapped lines `briefPx` counts — one rule, so the reserved
|
|
10646
|
+
* height and the drawn height cannot disagree.
|
|
10647
|
+
*/
|
|
10648
|
+
function BriefRow({ text, terminal }) {
|
|
10649
|
+
const [open, setOpen] = useState(false);
|
|
10650
|
+
if (!terminal) return /* @__PURE__ */ jsx("div", {
|
|
10651
|
+
"data-slot": "brief",
|
|
10652
|
+
className: "px-4 py-2 text-body-sm whitespace-pre-wrap text-fg-2",
|
|
10653
|
+
children: text
|
|
10654
|
+
});
|
|
10655
|
+
return /* @__PURE__ */ jsx("div", {
|
|
10656
|
+
"data-slot": "brief",
|
|
10657
|
+
children: /* @__PURE__ */ jsx(Pressable, {
|
|
10658
|
+
onPress: () => setOpen((v) => !v),
|
|
10659
|
+
expanded: open,
|
|
10660
|
+
children: /* @__PURE__ */ jsx(Row, {
|
|
10661
|
+
glyph: ">",
|
|
10662
|
+
glyphTone: "blue",
|
|
10663
|
+
tone: "dim",
|
|
10664
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
10665
|
+
className: cn("whitespace-pre-wrap", !open && "term-brief-clip"),
|
|
10666
|
+
style: open ? void 0 : { WebkitLineClamp: 4 },
|
|
10667
|
+
children: text
|
|
10668
|
+
})
|
|
10669
|
+
})
|
|
10670
|
+
})
|
|
10671
|
+
});
|
|
10672
|
+
}
|
|
10673
|
+
function TaskRow({ block, fileUrl, onOpenSubagent }) {
|
|
10674
|
+
const [open, setOpen] = useState(false);
|
|
10675
|
+
const reveal = useRevealOnOpen(open);
|
|
10676
|
+
const children = useMemo(() => taskChildItems(block), [block]);
|
|
10677
|
+
const brief = children.some((item) => item.kind === "user") ? void 0 : taskBrief(block.task);
|
|
10678
|
+
const busy = taskBusy(block.task, children);
|
|
10679
|
+
const failed = taskFailed(block.task);
|
|
10680
|
+
const pulse = usePulse(busy);
|
|
10681
|
+
const row = /* @__PURE__ */ jsxs("div", {
|
|
10682
|
+
ref: reveal,
|
|
10683
|
+
className: open ? "term-open" : void 0,
|
|
10684
|
+
children: [/* @__PURE__ */ jsx(Pressable, {
|
|
10685
|
+
onPress: () => setOpen((v) => !v),
|
|
10686
|
+
expanded: open,
|
|
10687
|
+
children: /* @__PURE__ */ jsx(Row, {
|
|
10688
|
+
glyph: busy ? pulse : "●",
|
|
10689
|
+
glyphTone: failed ? "red" : busy ? "mark" : "dim",
|
|
10690
|
+
tone: failed ? "red" : "green",
|
|
10691
|
+
children: taskSummary(block.task, children)
|
|
10692
|
+
})
|
|
10693
|
+
}), open ? /* @__PURE__ */ jsxs("div", {
|
|
10694
|
+
className: "term-nested",
|
|
10695
|
+
children: [brief ? /* @__PURE__ */ jsx(BriefRow, {
|
|
10696
|
+
text: brief,
|
|
10697
|
+
terminal: true
|
|
10698
|
+
}) : null, block.children.map((leaf, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && blockNeedsBlank(block.children[index - 1], leaf) ? /* @__PURE__ */ jsx(Blank, {}) : null, "run" in leaf ? /* @__PURE__ */ jsx(ToolRunRow, { items: leaf.run }) : /* @__PURE__ */ jsx(TerminalItemView, {
|
|
10699
|
+
item: leaf.item,
|
|
10700
|
+
fileUrl
|
|
10701
|
+
})] }, leaf.key))]
|
|
10702
|
+
}) : null]
|
|
10703
|
+
});
|
|
10704
|
+
return onOpenSubagent === void 0 ? row : /* @__PURE__ */ jsx(WithActions, {
|
|
10705
|
+
actions: /* @__PURE__ */ jsx(OpenSubagentAction, { onOpen: () => onOpenSubagent(block.task.id) }),
|
|
10706
|
+
children: row
|
|
10707
|
+
});
|
|
10708
|
+
}
|
|
9791
10709
|
/** When the current run began — the clock the working line counts from. Held
|
|
9792
10710
|
* here, not in the row, because the row comes and goes within a single turn (it
|
|
9793
10711
|
* hides the moment text streams) and a clock restarting at every tool call
|
|
@@ -9819,9 +10737,12 @@ function TerminalTranscript({ state, fileUrl, fontSize, lineHeight, affordances,
|
|
|
9819
10737
|
affordances,
|
|
9820
10738
|
bleed: "1ch",
|
|
9821
10739
|
className: cn("term-transcript", className),
|
|
9822
|
-
children: [blocks.map((block, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && blockNeedsBlank(blocks[index - 1], block) ? /* @__PURE__ */ jsx(Blank, {}) : null, "run" in block ? /* @__PURE__ */ jsx(ToolRunRow, { items: block.run }) : /* @__PURE__ */ jsx(TerminalItemView, {
|
|
10740
|
+
children: [blocks.map((block, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && blockNeedsBlank(blocks[index - 1], block) ? /* @__PURE__ */ jsx(Blank, {}) : null, "run" in block ? /* @__PURE__ */ jsx(ToolRunRow, { items: block.run }) : "item" in block ? /* @__PURE__ */ jsx(TerminalItemView, {
|
|
9823
10741
|
item: block.item,
|
|
9824
10742
|
fileUrl
|
|
10743
|
+
}) : /* @__PURE__ */ jsx(TaskRow, {
|
|
10744
|
+
block,
|
|
10745
|
+
fileUrl
|
|
9825
10746
|
})] }, block.key)), working(state) ? /* @__PURE__ */ jsxs(Fragment$1, { children: [state.items.length > 0 ? /* @__PURE__ */ jsx(Blank, {}) : null, /* @__PURE__ */ jsx(WorkingRow, {
|
|
9826
10747
|
label: state.status === "starting" ? "Starting…" : "Working…",
|
|
9827
10748
|
startedAt: runStartedAt,
|
|
@@ -10031,9 +10952,15 @@ function read(boundary, index) {
|
|
|
10031
10952
|
}
|
|
10032
10953
|
/** Rows produced inside a subagent (`parentToolUseId != null`) are stepped in
|
|
10033
10954
|
* behind a rule, so a Task's own output reads as belonging to the tool call
|
|
10034
|
-
* above it rather than as the main thread carrying on.
|
|
10035
|
-
|
|
10036
|
-
|
|
10955
|
+
* above it rather than as the main thread carrying on.
|
|
10956
|
+
*
|
|
10957
|
+
* **Except inside that sub-agent's own frame**, where those same items are the
|
|
10958
|
+
* top level and there is no main thread to be an aside from — stepping every row
|
|
10959
|
+
* in would draw a rule down the whole surface saying "this happened somewhere
|
|
10960
|
+
* else" about the only thing on screen. */
|
|
10961
|
+
function nestedClass(item, frameParentId) {
|
|
10962
|
+
const parent = "parentToolUseId" in item ? item.parentToolUseId : void 0;
|
|
10963
|
+
return parent != null && parent !== frameParentId ? "border-l-2 border-border pl-3" : void 0;
|
|
10037
10964
|
}
|
|
10038
10965
|
/**
|
|
10039
10966
|
* A prompt row's sticky lane — the strip spanning its turn, leading with the
|
|
@@ -10133,10 +11060,10 @@ function StickyPromptLane({ top, height, gapClass, scrollRoot, index, measureRef
|
|
|
10133
11060
|
* transient UI state (an expanded tool card, an opened reasoning block) resets
|
|
10134
11061
|
* once the row scrolls far enough away to unmount.
|
|
10135
11062
|
*/
|
|
10136
|
-
function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyPrompt, gap, fontSize, lineHeight, items, pendingApprovals, scrubber, scrubberMarks, affordances, fileUrl, attachmentUrl, hostImage, jumpToRecapRef, repinRef }) {
|
|
11063
|
+
function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyPrompt, frameParentId, onOpenSubagent, gap, fontSize, lineHeight, items, pendingApprovals, scrubber, scrubberMarks, affordances, fileUrl, attachmentUrl, hostImage, jumpToRecapRef, repinRef, reveal }) {
|
|
10137
11064
|
const stick = useStickToBottomContext();
|
|
10138
11065
|
const [scrollElement, setScrollElement] = useState(null);
|
|
10139
|
-
const promptRows = useMemo(() => rows.flatMap((row, index) => "item" in row && row.item.kind === "user" ? [index] : []), [rows]);
|
|
11066
|
+
const promptRows = useMemo(() => rows.flatMap((row, index) => "item" in row && row.item.kind === "user" && parentOf(row.item) === void 0 ? [index] : []), [rows]);
|
|
10140
11067
|
const pinRef = useRef({
|
|
10141
11068
|
enabled: false,
|
|
10142
11069
|
promptRows: []
|
|
@@ -10185,7 +11112,8 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
10185
11112
|
if (terminal && epoch) {
|
|
10186
11113
|
const row = rows[index];
|
|
10187
11114
|
const gapPx = index > 0 && gapBefore(rows, index) ? epoch.line : 0;
|
|
10188
|
-
if (row &&
|
|
11115
|
+
if (row && "text" in row && row.key === "brief") return briefPx(row.text, epoch) + gapPx;
|
|
11116
|
+
if (row && !("line" in row)) return estimateBlockPx(row, epoch) + gapPx;
|
|
10189
11117
|
return epoch.line + gapPx;
|
|
10190
11118
|
}
|
|
10191
11119
|
return (terminal ? 36 : 100) + gap.px;
|
|
@@ -10239,6 +11167,14 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
10239
11167
|
jumpToRecapRef,
|
|
10240
11168
|
repinRef
|
|
10241
11169
|
});
|
|
11170
|
+
const revealNonce = reveal?.nonce;
|
|
11171
|
+
const revealId = reveal?.toolUseId;
|
|
11172
|
+
useEffect(() => {
|
|
11173
|
+
if (revealId === void 0) return;
|
|
11174
|
+
const itemIndex = items.findIndex((item) => item.kind === "tool_call" && item.id === revealId);
|
|
11175
|
+
if (itemIndex < 0) return;
|
|
11176
|
+
jumpToRow(rowIndexForItem(rows, itemIndex), "start");
|
|
11177
|
+
}, [revealNonce]);
|
|
10242
11178
|
const scrubInteractive = resolveAffordances(affordances).hover;
|
|
10243
11179
|
const recapIndex = rows.findIndex((row) => row.key === "recap");
|
|
10244
11180
|
const recapRow = recapIndex >= 0 ? {
|
|
@@ -10306,7 +11242,7 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
10306
11242
|
className: cn(read(boundary, row.index) && "opacity-45"),
|
|
10307
11243
|
children: /* @__PURE__ */ jsx(ToolRunRow, { items: row.run })
|
|
10308
11244
|
}) : "item" in row ? /* @__PURE__ */ jsx("div", {
|
|
10309
|
-
className: cn(read(boundary, row.index) && "opacity-45", nestedClass(row.item)),
|
|
11245
|
+
className: cn(read(boundary, row.index) && "opacity-45", nestedClass(row.item, frameParentId)),
|
|
10310
11246
|
children: /* @__PURE__ */ jsx(TranscriptItemView, {
|
|
10311
11247
|
item: row.item,
|
|
10312
11248
|
fileUrl,
|
|
@@ -10314,12 +11250,22 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
10314
11250
|
hostImage,
|
|
10315
11251
|
terminal
|
|
10316
11252
|
})
|
|
10317
|
-
}) : /* @__PURE__ */ jsx(
|
|
11253
|
+
}) : "text" in row && row.key === "brief" ? /* @__PURE__ */ jsx(BriefRow, {
|
|
11254
|
+
text: row.text,
|
|
11255
|
+
terminal
|
|
11256
|
+
}) : "line" in row ? /* @__PURE__ */ jsx(RecapRow, {
|
|
10318
11257
|
line: row.line,
|
|
10319
11258
|
since,
|
|
10320
11259
|
terminal
|
|
11260
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
11261
|
+
className: cn(read(boundary, row.index) && "opacity-45"),
|
|
11262
|
+
children: /* @__PURE__ */ jsx(TaskRow, {
|
|
11263
|
+
block: row,
|
|
11264
|
+
fileUrl,
|
|
11265
|
+
onOpenSubagent
|
|
11266
|
+
})
|
|
10321
11267
|
});
|
|
10322
|
-
if (terminal && stickyPrompt && "item" in row && row.item.kind === "user") {
|
|
11268
|
+
if (terminal && stickyPrompt && "item" in row && row.item.kind === "user" && parentOf(row.item) === void 0) {
|
|
10323
11269
|
const next = promptRows.find((index) => index > virtualRow.index);
|
|
10324
11270
|
const laneEnd = next === void 0 ? virtualizer.getTotalSize() : measurements[next]?.start ?? virtualRow.start;
|
|
10325
11271
|
return /* @__PURE__ */ jsx(StickyPromptLane, {
|
|
@@ -10344,7 +11290,9 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
10344
11290
|
pendingApprovals,
|
|
10345
11291
|
recapRow,
|
|
10346
11292
|
bookmarks: scrubberMarks ?? [],
|
|
11293
|
+
frameParentId,
|
|
10347
11294
|
rowIndexFor: (itemIndex) => rowIndexForItem(rows, itemIndex),
|
|
11295
|
+
positionInRow: (itemIndex) => positionInRow(rows, itemIndex),
|
|
10348
11296
|
offsetOfRow: (rowIndex) => virtualizer.measurementsCache[rowIndex]?.start ?? 0,
|
|
10349
11297
|
sizeOfRow: (rowIndex) => virtualizer.measurementsCache[rowIndex]?.size ?? 0,
|
|
10350
11298
|
totalSize: virtualizer.getTotalSize(),
|
|
@@ -10357,28 +11305,40 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
10357
11305
|
}), scrollElement.parentElement) : null]
|
|
10358
11306
|
});
|
|
10359
11307
|
}
|
|
10360
|
-
function Transcript({ state, fileUrl, attachmentUrl, canBrowseFiles, hostImage, variant = "cards", density = "comfortable", fontSize, lineHeight, affordances, stickyPrompt = false, scrubber, scrubberMarks, replaying = false, catchUp, jumpToRecapRef, repinRef, className }) {
|
|
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 }) {
|
|
10361
11309
|
const terminal = variant === "terminal";
|
|
11310
|
+
const items = useMemo(() => frame ? subagentItems(state.items, frame.parentToolUseId) : state.items, [state.items, frame]);
|
|
11311
|
+
const frameTask = useMemo(() => frame ? state.items.find((item) => item.kind === "tool_call" && item.id === frame.parentToolUseId) : void 0, [state.items, frame]);
|
|
10362
11312
|
const gap = ROW_GAP[variant][density];
|
|
10363
11313
|
const runStartedAt = useRunStart(state.status);
|
|
10364
|
-
const boundary = catchUp && catchUp.from > 0 && catchUp.from < state.items.length ? catchUp.from : void 0;
|
|
11314
|
+
const boundary = !frame && catchUp && catchUp.from > 0 && catchUp.from < state.items.length ? catchUp.from : void 0;
|
|
10365
11315
|
const recap = useMemo(() => boundary === void 0 ? void 0 : recapLine(summarizeSince(state, boundary)), [state, boundary]);
|
|
11316
|
+
const brief = useMemo(() => frame && frameTask && !items.some((item) => item.kind === "user") ? taskBrief(frameTask) : void 0, [
|
|
11317
|
+
frame,
|
|
11318
|
+
frameTask,
|
|
11319
|
+
items
|
|
11320
|
+
]);
|
|
10366
11321
|
const rows = useMemo(() => {
|
|
10367
|
-
const fold = (from, to) => terminalBlocks(
|
|
10368
|
-
|
|
11322
|
+
const fold = (from, to) => terminalBlocks(items.slice(from, to), from, terminal);
|
|
11323
|
+
const lead = brief ? [{
|
|
11324
|
+
key: "brief",
|
|
11325
|
+
text: brief
|
|
11326
|
+
}] : [];
|
|
11327
|
+
if (boundary === void 0 || !recap) return [...lead, ...fold(0, items.length)];
|
|
10369
11328
|
return [
|
|
10370
11329
|
...fold(0, boundary),
|
|
10371
11330
|
{
|
|
10372
11331
|
key: "recap",
|
|
10373
11332
|
line: recap
|
|
10374
11333
|
},
|
|
10375
|
-
...fold(boundary,
|
|
11334
|
+
...fold(boundary, items.length)
|
|
10376
11335
|
];
|
|
10377
11336
|
}, [
|
|
10378
|
-
|
|
11337
|
+
items,
|
|
10379
11338
|
boundary,
|
|
10380
11339
|
recap,
|
|
10381
|
-
terminal
|
|
11340
|
+
terminal,
|
|
11341
|
+
brief
|
|
10382
11342
|
]);
|
|
10383
11343
|
return /* @__PURE__ */ jsx(TranscriptVariantProvider, {
|
|
10384
11344
|
value: variant,
|
|
@@ -10392,43 +11352,53 @@ function Transcript({ state, fileUrl, attachmentUrl, canBrowseFiles, hostImage,
|
|
|
10392
11352
|
fontSize,
|
|
10393
11353
|
lineHeight,
|
|
10394
11354
|
affordances,
|
|
10395
|
-
children: [
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
replaying,
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
|
|
10430
|
-
|
|
10431
|
-
|
|
11355
|
+
children: [
|
|
11356
|
+
frame ? frameTask === void 0 && !replaying ? /* @__PURE__ */ jsx("div", {
|
|
11357
|
+
className: cn(terminal ? "term-row text-fg-4" : "p-4 text-body-sm text-fg-4"),
|
|
11358
|
+
children: "This sub-agent's work is not in this transcript."
|
|
11359
|
+
}) : null : items.length === 0 && state.status !== "starting" ? /* @__PURE__ */ jsx(SessionEmptyState, {
|
|
11360
|
+
cwd: state.cwd,
|
|
11361
|
+
hasCommands: !!state.commands?.length,
|
|
11362
|
+
hasSkills: !!state.skills?.some((s) => s.enabled),
|
|
11363
|
+
canBrowseFiles
|
|
11364
|
+
}) : null,
|
|
11365
|
+
frame && frameTask === void 0 && !replaying ? null : /* @__PURE__ */ jsx(TranscriptRows, {
|
|
11366
|
+
rows,
|
|
11367
|
+
boundary,
|
|
11368
|
+
since: catchUp?.since,
|
|
11369
|
+
terminal,
|
|
11370
|
+
replaying,
|
|
11371
|
+
stickyPrompt: !frame && stickyPrompt,
|
|
11372
|
+
gap,
|
|
11373
|
+
fontSize,
|
|
11374
|
+
lineHeight,
|
|
11375
|
+
items,
|
|
11376
|
+
pendingApprovals: state.pendingApprovals,
|
|
11377
|
+
scrubber,
|
|
11378
|
+
scrubberMarks: frame ? void 0 : scrubberMarks,
|
|
11379
|
+
affordances,
|
|
11380
|
+
fileUrl,
|
|
11381
|
+
attachmentUrl,
|
|
11382
|
+
hostImage,
|
|
11383
|
+
jumpToRecapRef,
|
|
11384
|
+
repinRef,
|
|
11385
|
+
reveal: frame ? void 0 : reveal,
|
|
11386
|
+
frameParentId: frame?.parentToolUseId,
|
|
11387
|
+
onOpenSubagent: frame ? void 0 : onOpenSubagent
|
|
11388
|
+
}),
|
|
11389
|
+
(frame ? frameTask !== void 0 && taskBusy(frameTask, items) : showLoader(state)) ? terminal ? /* @__PURE__ */ jsxs(Fragment$1, { children: [state.items.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
11390
|
+
className: "term-blank",
|
|
11391
|
+
"aria-hidden": true
|
|
11392
|
+
}) : null, /* @__PURE__ */ jsx(WorkingRow, {
|
|
11393
|
+
label: state.status === "starting" ? "Starting…" : "Working…",
|
|
11394
|
+
startedAt: runStartedAt,
|
|
11395
|
+
tokens: state.contextUsage?.totalTokens
|
|
11396
|
+
})] }) : /* @__PURE__ */ jsx(Loader, {
|
|
11397
|
+
label: state.status === "starting" ? "Starting session…" : void 0,
|
|
11398
|
+
startedAt: runStartedAt,
|
|
11399
|
+
tokens: state.contextUsage?.totalTokens
|
|
11400
|
+
}) : null
|
|
11401
|
+
]
|
|
10432
11402
|
})
|
|
10433
11403
|
}),
|
|
10434
11404
|
/* @__PURE__ */ jsx(ConversationScrollButton, {}),
|
|
@@ -10650,18 +11620,71 @@ const INTERACTIVE = [
|
|
|
10650
11620
|
* the engine name — an absent capability hides the control instead of offering
|
|
10651
11621
|
* one that can only fail.
|
|
10652
11622
|
*/
|
|
10653
|
-
function SessionPanel({ client, sessionId, header, panelSurface = "internal", statusSurface = "internal", statusPlacement = "top", onOpenPanel, onVitals, transcriptVariant = "cards", transcriptDensity = "comfortable", transcriptFont = "sans", affordances, terminalMetrics, scrubber = false, scrubberMarks, stickyPrompt = false, controlsSurface = "internal", onControls, focusComposerOnClick = false, unseen, readOnly = false, toolHost, cacheTranscript, className }) {
|
|
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 }) {
|
|
10654
11624
|
const external = panelSurface === "external";
|
|
10655
11625
|
const statusExternal = statusSurface === "external";
|
|
10656
11626
|
const controlsInStatus = controlsSurface === "status" && !statusExternal;
|
|
10657
11627
|
const controlsExternal = controlsSurface === "external" || controlsInStatus;
|
|
10658
11628
|
const [protocolError, setProtocolError] = useState(void 0);
|
|
10659
11629
|
const [panel, setPanel] = useState();
|
|
10660
|
-
|
|
11630
|
+
/**
|
|
11631
|
+
* The sub-agent takeover: which `Task` call the body is currently showing, or
|
|
11632
|
+
* undefined for the conversation.
|
|
11633
|
+
*
|
|
11634
|
+
* Deliberately **not** a `Panel` member. Those route outward under
|
|
11635
|
+
* `panelSurface: 'external'` so a host can draw them natively — and a host
|
|
11636
|
+
* cannot draw this one: it is a transcript, and the transcript is in here. It
|
|
11637
|
+
* is a mode of the conversation surface, not a screen over it.
|
|
11638
|
+
*/
|
|
11639
|
+
const [subagentId, setSubagentId] = useState(void 0);
|
|
11640
|
+
/**
|
|
11641
|
+
* Where to land on the way back — the row of the Task you entered from,
|
|
11642
|
+
* asked for through the ordinary reveal seam.
|
|
11643
|
+
*
|
|
11644
|
+
* The alternative was keeping the main transcript mounted-but-hidden to
|
|
11645
|
+
* preserve its scroll offset, which costs two live virtualizers and an
|
|
11646
|
+
* absolute-position layout on a panel whose height just changed (the composer
|
|
11647
|
+
* came back). Re-revealing is both cheaper and more honest: you return to the
|
|
11648
|
+
* row you left from rather than to wherever you happened to be scrolled.
|
|
11649
|
+
*/
|
|
11650
|
+
const [returnReveal, setReturnReveal] = useState(void 0);
|
|
11651
|
+
const { state, connection, replaying, protocolMismatch, models, effectiveModel, handle, send, approve, deny, interrupt, setModel, setPermissionMode, reconnectNow, loadFullResult } = useClaudeSession(client, sessionId, {
|
|
10661
11652
|
onProtocolError: setProtocolError,
|
|
10662
11653
|
cacheTranscript
|
|
10663
11654
|
});
|
|
10664
11655
|
useEffect(() => setProtocolError(void 0), [sessionId]);
|
|
11656
|
+
useEffect(() => {
|
|
11657
|
+
setSubagentId(void 0);
|
|
11658
|
+
setReturnReveal(void 0);
|
|
11659
|
+
}, [sessionId]);
|
|
11660
|
+
const openSubagentNonce = openSubagent?.nonce;
|
|
11661
|
+
const openSubagentId = openSubagent?.toolUseId;
|
|
11662
|
+
useEffect(() => {
|
|
11663
|
+
if (openSubagentId === void 0) return;
|
|
11664
|
+
setSubagentId(openSubagentId);
|
|
11665
|
+
}, [openSubagentNonce]);
|
|
11666
|
+
const revealNonce = reveal?.nonce;
|
|
11667
|
+
useEffect(() => {
|
|
11668
|
+
if (revealNonce === void 0) return;
|
|
11669
|
+
setSubagentId(void 0);
|
|
11670
|
+
}, [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
|
+
useEffect(() => {
|
|
11680
|
+
if (subagentId === void 0) return;
|
|
11681
|
+
const onKey = (event) => {
|
|
11682
|
+
if (event.key !== "Escape" || event.defaultPrevented) return;
|
|
11683
|
+
leaveSubagent();
|
|
11684
|
+
};
|
|
11685
|
+
window.addEventListener("keydown", onKey);
|
|
11686
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
11687
|
+
}, [subagentId, leaveSubagent]);
|
|
10665
11688
|
const [caughtUp, setCaughtUp] = useState(false);
|
|
10666
11689
|
useEffect(() => {
|
|
10667
11690
|
setCaughtUp(false);
|
|
@@ -10682,6 +11705,12 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
|
|
|
10682
11705
|
}, [reconnectNow]);
|
|
10683
11706
|
useToolCallHost(handle, toolHost === false ? { enabled: false } : toolHost);
|
|
10684
11707
|
const terminal = transcriptVariant === "terminal";
|
|
11708
|
+
const subagentFrameItems = useMemo(() => subagentId === void 0 ? [] : subagentItems(state.items, subagentId), [state.items, subagentId]);
|
|
11709
|
+
const subagentTask = useMemo(() => subagentId === void 0 ? void 0 : state.items.find((item) => item.kind === "tool_call" && item.id === subagentId), [state.items, subagentId]);
|
|
11710
|
+
const subagentFallbackLabel = useMemo(() => {
|
|
11711
|
+
const record = state.session?.subagents?.find((sub) => sub.toolUseId === subagentId);
|
|
11712
|
+
return record ? subagentLabel(record) : "Sub-agent";
|
|
11713
|
+
}, [state.session, subagentId]);
|
|
10685
11714
|
const capabilities = state.capabilities;
|
|
10686
11715
|
const { usage: profileUsage } = useProfileUsage(client, state.session?.profile, { enabled: capabilities.rateLimits });
|
|
10687
11716
|
const usage = useMemo(() => mergeUsage({
|
|
@@ -10762,6 +11791,7 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
|
|
|
10762
11791
|
const hostFiles = useHostFileSearch(client, state.cwd);
|
|
10763
11792
|
const windows = useMemo(() => orderUsageWindows(usage), [usage]);
|
|
10764
11793
|
const hostImage = useHostImage(client, sessionId, state.producedFiles);
|
|
11794
|
+
const resultImages = useToolResultImages(client, sessionId);
|
|
10765
11795
|
const composerRef = useRef(null);
|
|
10766
11796
|
const jumpToRecap = useRef(null);
|
|
10767
11797
|
const repinTranscript = useRef(null);
|
|
@@ -10858,178 +11888,196 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
|
|
|
10858
11888
|
value: transcriptVariant,
|
|
10859
11889
|
children: /* @__PURE__ */ jsx(TranscriptDensityProvider, {
|
|
10860
11890
|
value: transcriptDensity,
|
|
10861
|
-
children: /* @__PURE__ */
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
level: "warning",
|
|
11891
|
+
children: /* @__PURE__ */ jsx(ToolResultFetchProvider, {
|
|
11892
|
+
value: loadFullResult,
|
|
11893
|
+
children: /* @__PURE__ */ jsx(ToolResultImageProvider, {
|
|
11894
|
+
value: resultImages,
|
|
11895
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
11896
|
+
"data-slot": "session-panel",
|
|
11897
|
+
"data-agent-font": transcriptFont,
|
|
11898
|
+
onClick: handleClick,
|
|
11899
|
+
className: cn("flex h-full min-h-0 flex-col overflow-hidden bg-bg", className),
|
|
10871
11900
|
children: [
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
}
|
|
10917
|
-
|
|
10918
|
-
|
|
11901
|
+
headerTakesActions ? header({ actions: menu }) : header,
|
|
11902
|
+
statusPlacement === "top" ? statusBar : null,
|
|
11903
|
+
protocolMismatch !== void 0 ? /* @__PURE__ */ jsxs(Notice, {
|
|
11904
|
+
level: "warning",
|
|
11905
|
+
children: [
|
|
11906
|
+
"Server speaks protocol v",
|
|
11907
|
+
protocolMismatch,
|
|
11908
|
+
", this build renders v",
|
|
11909
|
+
PROTOCOL_VERSION,
|
|
11910
|
+
". Some events may not render."
|
|
11911
|
+
]
|
|
11912
|
+
}) : null,
|
|
11913
|
+
protocolError ? /* @__PURE__ */ jsx(Notice, {
|
|
11914
|
+
level: "error",
|
|
11915
|
+
onDismiss: () => setProtocolError(void 0),
|
|
11916
|
+
children: protocolError
|
|
11917
|
+
}) : null,
|
|
11918
|
+
subagentId !== void 0 ? /* @__PURE__ */ jsx(SubagentStrip, {
|
|
11919
|
+
task: subagentTask,
|
|
11920
|
+
items: subagentFrameItems,
|
|
11921
|
+
label: subagentFallbackLabel,
|
|
11922
|
+
onBack: leaveSubagent,
|
|
11923
|
+
terminal,
|
|
11924
|
+
fontSize: terminalMetrics?.fontSize,
|
|
11925
|
+
lineHeight: terminalMetrics?.lineHeight
|
|
11926
|
+
}) : null,
|
|
11927
|
+
/* @__PURE__ */ jsx(Transcript, {
|
|
11928
|
+
state,
|
|
11929
|
+
fileUrl: sessionId ? (path) => client.sessionFileUrl(sessionId, path) : void 0,
|
|
11930
|
+
attachmentUrl: sessionId ? (id) => client.attachmentUrl(sessionId, id) : void 0,
|
|
11931
|
+
canBrowseFiles: hostFiles.available,
|
|
11932
|
+
hostImage,
|
|
11933
|
+
variant: transcriptVariant,
|
|
11934
|
+
density: transcriptDensity,
|
|
11935
|
+
fontSize: terminalMetrics?.fontSize,
|
|
11936
|
+
lineHeight: terminalMetrics?.lineHeight,
|
|
11937
|
+
affordances,
|
|
11938
|
+
stickyPrompt,
|
|
11939
|
+
scrubber,
|
|
11940
|
+
scrubberMarks,
|
|
11941
|
+
replaying,
|
|
11942
|
+
catchUp: catchUp && newCount > 0 ? {
|
|
11943
|
+
from: catchUp.itemCount,
|
|
11944
|
+
since: catchUp.since
|
|
11945
|
+
} : void 0,
|
|
11946
|
+
reveal: returnReveal ?? reveal,
|
|
11947
|
+
frame: subagentId === void 0 ? void 0 : { parentToolUseId: subagentId },
|
|
11948
|
+
onOpenSubagent: setSubagentId,
|
|
11949
|
+
jumpToRecapRef: jumpToRecap,
|
|
11950
|
+
repinRef: repinTranscript
|
|
11951
|
+
}, subagentId ?? "session"),
|
|
11952
|
+
catchUp && newCount > 0 && !replaying && subagentId === void 0 ? /* @__PURE__ */ jsx("div", {
|
|
11953
|
+
className: "px-3 pb-1",
|
|
11954
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
11955
|
+
"data-slot": "catch-up",
|
|
11956
|
+
className: "mx-auto flex w-full max-w-[var(--wd-content-max-w,48rem)] items-center gap-2 text-label text-fg-3",
|
|
10919
11957
|
children: [
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
|
|
11958
|
+
/* @__PURE__ */ jsx("span", {
|
|
11959
|
+
"aria-hidden": true,
|
|
11960
|
+
className: cn("select-none", terminal ? "text-fg-3" : "text-accent"),
|
|
11961
|
+
children: "※"
|
|
11962
|
+
}),
|
|
11963
|
+
/* @__PURE__ */ jsxs("span", {
|
|
11964
|
+
className: "min-w-0 flex-1 truncate",
|
|
11965
|
+
children: [
|
|
11966
|
+
newCount,
|
|
11967
|
+
" new ",
|
|
11968
|
+
newCount === 1 ? "row" : "rows",
|
|
11969
|
+
catchUp.since !== void 0 ? ` since you were last here` : ""
|
|
11970
|
+
]
|
|
11971
|
+
}),
|
|
11972
|
+
/* @__PURE__ */ jsx("button", {
|
|
11973
|
+
type: "button",
|
|
11974
|
+
onClick: () => jumpToRecap.current?.(),
|
|
11975
|
+
className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
|
|
11976
|
+
children: "jump"
|
|
11977
|
+
}),
|
|
11978
|
+
/* @__PURE__ */ jsx("button", {
|
|
11979
|
+
type: "button",
|
|
11980
|
+
onClick: () => setCaughtUp(true),
|
|
11981
|
+
className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
|
|
11982
|
+
children: "dismiss"
|
|
11983
|
+
})
|
|
10924
11984
|
]
|
|
11985
|
+
})
|
|
11986
|
+
}) : null,
|
|
11987
|
+
!readOnly && capabilities.interactiveApprovals && state.pendingApprovals.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
11988
|
+
className: cn(terminal ? "pb-2" : "px-3 pb-2"),
|
|
11989
|
+
children: /* @__PURE__ */ jsx(PromptSurface, {
|
|
11990
|
+
terminal,
|
|
11991
|
+
metrics: terminalMetrics,
|
|
11992
|
+
affordances,
|
|
11993
|
+
children: state.pendingApprovals.map((request) => {
|
|
11994
|
+
const isQuestion = request.toolName === "AskUserQuestion" && parseUserQuestions(request.input).length > 0;
|
|
11995
|
+
if (terminal) return isQuestion ? /* @__PURE__ */ jsx(TerminalQuestionPrompt, {
|
|
11996
|
+
request,
|
|
11997
|
+
onAnswer: approve,
|
|
11998
|
+
onDismiss: (id) => deny(id, "Question dismissed by user")
|
|
11999
|
+
}, request.id) : /* @__PURE__ */ jsx(TerminalPermissionPrompt, {
|
|
12000
|
+
request,
|
|
12001
|
+
onApprove: approve,
|
|
12002
|
+
onDeny: deny
|
|
12003
|
+
}, request.id);
|
|
12004
|
+
return isQuestion ? /* @__PURE__ */ jsx(QuestionPrompt, {
|
|
12005
|
+
request,
|
|
12006
|
+
onAnswer: approve,
|
|
12007
|
+
onDismiss: (id) => deny(id, "Question dismissed by user")
|
|
12008
|
+
}, request.id) : /* @__PURE__ */ jsx(PermissionPrompt, {
|
|
12009
|
+
request,
|
|
12010
|
+
onApprove: approve,
|
|
12011
|
+
onDeny: deny
|
|
12012
|
+
}, request.id);
|
|
12013
|
+
})
|
|
12014
|
+
})
|
|
12015
|
+
}) : null,
|
|
12016
|
+
readOnly || subagentId !== void 0 ? null : /* @__PURE__ */ jsx(Composer, {
|
|
12017
|
+
ref: composerRef,
|
|
12018
|
+
onSend: handleSend,
|
|
12019
|
+
onInterrupt: interrupt,
|
|
12020
|
+
busy,
|
|
12021
|
+
disabled: ended || !sessionId,
|
|
12022
|
+
commands: capabilities.slashCommands ? commands : void 0,
|
|
12023
|
+
skills: capabilities.skillsList ? state.skills : void 0,
|
|
12024
|
+
attachments,
|
|
12025
|
+
onSearchFiles: hostFiles.available ? (query, options) => hostFiles.search(query, {
|
|
12026
|
+
...options,
|
|
12027
|
+
limit: 8
|
|
12028
|
+
}) : void 0,
|
|
12029
|
+
layout: controlsExternal ? "inline" : "stacked",
|
|
12030
|
+
toolbar: controlsExternal ? void 0 : sessionControls,
|
|
12031
|
+
fontSize: terminalMetrics?.fontSize,
|
|
12032
|
+
lineHeight: terminalMetrics?.lineHeight,
|
|
12033
|
+
affordances
|
|
12034
|
+
}),
|
|
12035
|
+
statusPlacement === "bottom" ? statusBar : null,
|
|
12036
|
+
!external ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
12037
|
+
/* @__PURE__ */ jsx(SessionInfoDialog, {
|
|
12038
|
+
state,
|
|
12039
|
+
client,
|
|
12040
|
+
sessionId,
|
|
12041
|
+
open: panel === "info",
|
|
12042
|
+
onOpenChange: (next) => setPanel(next ? "info" : void 0)
|
|
12043
|
+
}),
|
|
12044
|
+
/* @__PURE__ */ jsx(ContextDialog, {
|
|
12045
|
+
usage: state.contextUsage,
|
|
12046
|
+
open: panel === "context",
|
|
12047
|
+
onOpenChange: (next) => setPanel(next ? "context" : void 0)
|
|
12048
|
+
}),
|
|
12049
|
+
/* @__PURE__ */ jsx(UsageDialog, {
|
|
12050
|
+
rateLimits: windows,
|
|
12051
|
+
subscriptionType: state.subscriptionType,
|
|
12052
|
+
engine: state.engine ?? "claude",
|
|
12053
|
+
totalCostUsd: state.totalCostUsd,
|
|
12054
|
+
updatedAt: usageUpdatedAt,
|
|
12055
|
+
open: panel === "usage",
|
|
12056
|
+
onOpenChange: (next) => setPanel(next ? "usage" : void 0)
|
|
12057
|
+
}),
|
|
12058
|
+
/* @__PURE__ */ jsx(McpDialog, {
|
|
12059
|
+
client,
|
|
12060
|
+
sessionId,
|
|
12061
|
+
canManageServers: capabilities.mcpServerActions,
|
|
12062
|
+
open: panel === "mcp",
|
|
12063
|
+
onOpenChange: (next) => setPanel(next ? "mcp" : void 0)
|
|
10925
12064
|
}),
|
|
10926
|
-
/* @__PURE__ */ jsx(
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
12065
|
+
/* @__PURE__ */ jsx(SkillsDialog, {
|
|
12066
|
+
skills: state.skills,
|
|
12067
|
+
open: panel === "skills",
|
|
12068
|
+
onOpenChange: (next) => setPanel(next ? "skills" : void 0),
|
|
12069
|
+
onUse: (skill) => composerRef.current?.insertText(skillPrompt(skill))
|
|
10931
12070
|
}),
|
|
10932
|
-
/* @__PURE__ */ jsx(
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
|
|
12071
|
+
/* @__PURE__ */ jsx(HostFilesDialog, {
|
|
12072
|
+
client,
|
|
12073
|
+
cwd: state.cwd,
|
|
12074
|
+
open: panel === "files",
|
|
12075
|
+
onOpenChange: (next) => setPanel(next ? "files" : void 0)
|
|
10937
12076
|
})
|
|
10938
|
-
]
|
|
10939
|
-
|
|
10940
|
-
})
|
|
10941
|
-
|
|
10942
|
-
className: cn(terminal ? "pb-2" : "px-3 pb-2"),
|
|
10943
|
-
children: /* @__PURE__ */ jsx(PromptSurface, {
|
|
10944
|
-
terminal,
|
|
10945
|
-
metrics: terminalMetrics,
|
|
10946
|
-
affordances,
|
|
10947
|
-
children: state.pendingApprovals.map((request) => {
|
|
10948
|
-
const isQuestion = request.toolName === "AskUserQuestion" && parseUserQuestions(request.input).length > 0;
|
|
10949
|
-
if (terminal) return isQuestion ? /* @__PURE__ */ jsx(TerminalQuestionPrompt, {
|
|
10950
|
-
request,
|
|
10951
|
-
onAnswer: approve,
|
|
10952
|
-
onDismiss: (id) => deny(id, "Question dismissed by user")
|
|
10953
|
-
}, request.id) : /* @__PURE__ */ jsx(TerminalPermissionPrompt, {
|
|
10954
|
-
request,
|
|
10955
|
-
onApprove: approve,
|
|
10956
|
-
onDeny: deny
|
|
10957
|
-
}, request.id);
|
|
10958
|
-
return isQuestion ? /* @__PURE__ */ jsx(QuestionPrompt, {
|
|
10959
|
-
request,
|
|
10960
|
-
onAnswer: approve,
|
|
10961
|
-
onDismiss: (id) => deny(id, "Question dismissed by user")
|
|
10962
|
-
}, request.id) : /* @__PURE__ */ jsx(PermissionPrompt, {
|
|
10963
|
-
request,
|
|
10964
|
-
onApprove: approve,
|
|
10965
|
-
onDeny: deny
|
|
10966
|
-
}, request.id);
|
|
10967
|
-
})
|
|
10968
|
-
})
|
|
10969
|
-
}) : null,
|
|
10970
|
-
readOnly ? null : /* @__PURE__ */ jsx(Composer, {
|
|
10971
|
-
ref: composerRef,
|
|
10972
|
-
onSend: handleSend,
|
|
10973
|
-
onInterrupt: interrupt,
|
|
10974
|
-
busy,
|
|
10975
|
-
disabled: ended || !sessionId,
|
|
10976
|
-
commands: capabilities.slashCommands ? commands : void 0,
|
|
10977
|
-
skills: capabilities.skillsList ? state.skills : void 0,
|
|
10978
|
-
attachments,
|
|
10979
|
-
onSearchFiles: hostFiles.available ? (query, options) => hostFiles.search(query, {
|
|
10980
|
-
...options,
|
|
10981
|
-
limit: 8
|
|
10982
|
-
}) : void 0,
|
|
10983
|
-
layout: controlsExternal ? "inline" : "stacked",
|
|
10984
|
-
toolbar: controlsExternal ? void 0 : sessionControls,
|
|
10985
|
-
fontSize: terminalMetrics?.fontSize,
|
|
10986
|
-
lineHeight: terminalMetrics?.lineHeight,
|
|
10987
|
-
affordances
|
|
10988
|
-
}),
|
|
10989
|
-
statusPlacement === "bottom" ? statusBar : null,
|
|
10990
|
-
!external ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
10991
|
-
/* @__PURE__ */ jsx(SessionInfoDialog, {
|
|
10992
|
-
state,
|
|
10993
|
-
client,
|
|
10994
|
-
sessionId,
|
|
10995
|
-
open: panel === "info",
|
|
10996
|
-
onOpenChange: (next) => setPanel(next ? "info" : void 0)
|
|
10997
|
-
}),
|
|
10998
|
-
/* @__PURE__ */ jsx(ContextDialog, {
|
|
10999
|
-
usage: state.contextUsage,
|
|
11000
|
-
open: panel === "context",
|
|
11001
|
-
onOpenChange: (next) => setPanel(next ? "context" : void 0)
|
|
11002
|
-
}),
|
|
11003
|
-
/* @__PURE__ */ jsx(UsageDialog, {
|
|
11004
|
-
rateLimits: windows,
|
|
11005
|
-
subscriptionType: state.subscriptionType,
|
|
11006
|
-
engine: state.engine ?? "claude",
|
|
11007
|
-
totalCostUsd: state.totalCostUsd,
|
|
11008
|
-
updatedAt: usageUpdatedAt,
|
|
11009
|
-
open: panel === "usage",
|
|
11010
|
-
onOpenChange: (next) => setPanel(next ? "usage" : void 0)
|
|
11011
|
-
}),
|
|
11012
|
-
/* @__PURE__ */ jsx(McpDialog, {
|
|
11013
|
-
client,
|
|
11014
|
-
sessionId,
|
|
11015
|
-
canManageServers: capabilities.mcpServerActions,
|
|
11016
|
-
open: panel === "mcp",
|
|
11017
|
-
onOpenChange: (next) => setPanel(next ? "mcp" : void 0)
|
|
11018
|
-
}),
|
|
11019
|
-
/* @__PURE__ */ jsx(SkillsDialog, {
|
|
11020
|
-
skills: state.skills,
|
|
11021
|
-
open: panel === "skills",
|
|
11022
|
-
onOpenChange: (next) => setPanel(next ? "skills" : void 0),
|
|
11023
|
-
onUse: (skill) => composerRef.current?.insertText(skillPrompt(skill))
|
|
11024
|
-
}),
|
|
11025
|
-
/* @__PURE__ */ jsx(HostFilesDialog, {
|
|
11026
|
-
client,
|
|
11027
|
-
cwd: state.cwd,
|
|
11028
|
-
open: panel === "files",
|
|
11029
|
-
onOpenChange: (next) => setPanel(next ? "files" : void 0)
|
|
11030
|
-
})
|
|
11031
|
-
] }) : null
|
|
11032
|
-
]
|
|
12077
|
+
] }) : null
|
|
12078
|
+
]
|
|
12079
|
+
})
|
|
12080
|
+
})
|
|
11033
12081
|
})
|
|
11034
12082
|
})
|
|
11035
12083
|
});
|
|
@@ -11122,6 +12170,6 @@ function Notice({ level, onDismiss, children }) {
|
|
|
11122
12170
|
});
|
|
11123
12171
|
}
|
|
11124
12172
|
//#endregion
|
|
11125
|
-
export {
|
|
12173
|
+
export { toolIcon as $, PERMISSION_MODES as A, MenuSeparator as At, skillPrompt as B, badgeVariants 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, buttonVariants 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, Input as Rt, SessionInfoDialog as S, DialogClose as St, QUESTION_BEHAVIORS as T, DialogRow as Tt, TerminalDiff as U, cn as Ut, TerminalSurface as V, Button as Vt, previewPatch as W, 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, Badge as zt };
|
|
11126
12174
|
|
|
11127
|
-
//# sourceMappingURL=SessionPanel-
|
|
12175
|
+
//# sourceMappingURL=SessionPanel-DPx8Iz8a.mjs.map
|