@workerdeck/ui 0.16.0 → 0.17.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-CnNYEX80.d.mts} +13 -1
- package/build/{SessionPanel-DII9MmQ8.mjs → SessionPanel-DMPhsNlW.mjs} +1015 -332
- package/build/SessionPanel-DMPhsNlW.mjs.map +1 -0
- package/build/index.d.mts +54 -3
- package/build/index.mjs +240 -17
- package/build/index.mjs.map +1 -1
- package/build/workspace.d.mts +1 -1
- package/build/workspace.mjs +1 -1
- package/package.json +8 -6
- package/src/components/agent/ProjectIcon.tsx +119 -0
- package/src/components/agent/SessionBrowser.tsx +84 -3
- package/src/components/agent/SessionPanel.tsx +25 -0
- package/src/components/agent/ToolCallCard.tsx +72 -5
- package/src/components/agent/Transcript.tsx +77 -7
- 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 +113 -22
- package/src/components/terminal/TerminalTranscript.tsx +80 -2
- package/src/components/terminal/blocks.ts +232 -0
- package/src/components/terminal/height.ts +49 -6
- package/src/components/terminal/image-box.ts +53 -0
- package/src/components/terminal/items.tsx +115 -78
- package/src/components/terminal/result-preview.ts +20 -6
- package/src/components/terminal/scrubber.tsx +146 -25
- package/src/components/terminal/tool-run.ts +133 -0
- package/src/index.ts +2 -1
- package/src/styles/terminal.css +75 -5
- package/build/SessionPanel-DII9MmQ8.mjs.map +0 -1
|
@@ -5021,6 +5021,32 @@ function scrollParent(from) {
|
|
|
5021
5021
|
return null;
|
|
5022
5022
|
}
|
|
5023
5023
|
//#endregion
|
|
5024
|
+
//#region src/components/terminal/image-box.ts
|
|
5025
|
+
/**
|
|
5026
|
+
* What the box says before the fetch lands.
|
|
5027
|
+
*
|
|
5028
|
+
* `bytes` is the decoded size the gateway stamped on the reference — the client
|
|
5029
|
+
* holds no bytes at all until it asks for them, so this is a number it cannot
|
|
5030
|
+
* compute, the same reason `total_chars` rides beside a truncated head.
|
|
5031
|
+
* `formatBytes` rather than a spelling of its own: the panel says "336.0 KB"
|
|
5032
|
+
* everywhere else, and a second byte formatter is a second thing to keep in
|
|
5033
|
+
* step.
|
|
5034
|
+
*/
|
|
5035
|
+
function imagePlaceholder(image) {
|
|
5036
|
+
return `image · ${formatBytes(image.bytes)}`;
|
|
5037
|
+
}
|
|
5038
|
+
/**
|
|
5039
|
+
* What it says when the fetch failed — a stale address after a dormant wake
|
|
5040
|
+
* (the route 404s rather than serving another call's pixels), a gateway too old
|
|
5041
|
+
* to know the route, a dropped connection.
|
|
5042
|
+
*
|
|
5043
|
+
* It occupies the same box, because the alternative is the row changing height
|
|
5044
|
+
* on a network failure. Silence is not an option here the way it is for
|
|
5045
|
+
* `HostImage`: that card names the host path in its result text, so a reader can
|
|
5046
|
+
* still find the picture; a replayed image part has no path to name.
|
|
5047
|
+
*/
|
|
5048
|
+
const IMAGE_UNAVAILABLE = "image unavailable";
|
|
5049
|
+
//#endregion
|
|
5024
5050
|
//#region src/components/terminal/result-preview.ts
|
|
5025
5051
|
/**
|
|
5026
5052
|
* How much of a tool result a collapsed row shows, and what it says it hid.
|
|
@@ -5056,7 +5082,16 @@ const PREVIEW_CHARS = 400;
|
|
|
5056
5082
|
* lines otherwise — a one-line JSON blob has no hidden lines to count, and
|
|
5057
5083
|
* "+0 lines" under a visibly cut-off row is worse than saying nothing.
|
|
5058
5084
|
*/
|
|
5059
|
-
|
|
5085
|
+
/**
|
|
5086
|
+
* `totalChars` is the **untruncated** length when the replay delivered only a
|
|
5087
|
+
* head (protocol's `ToolResultBlock.total_chars`). Passing it is not cosmetic:
|
|
5088
|
+
* computed from the head this row would say "… +7,600 chars" where the truth is
|
|
5089
|
+
* 641,003, and the wrong string is a *different pixel height* — which is exactly
|
|
5090
|
+
* the drift this module exists to prevent, since `height.ts` sizes the row by
|
|
5091
|
+
* wrapping this same text. Omitted for a whole result, where the lines are the
|
|
5092
|
+
* whole truth.
|
|
5093
|
+
*/
|
|
5094
|
+
function collapsedResult(lines, totalChars) {
|
|
5060
5095
|
const shown = [];
|
|
5061
5096
|
let chars = 0;
|
|
5062
5097
|
let cut = false;
|
|
@@ -5071,9 +5106,15 @@ function collapsedResult(lines) {
|
|
|
5071
5106
|
shown.push(line);
|
|
5072
5107
|
chars += line.length + 1;
|
|
5073
5108
|
}
|
|
5109
|
+
const held = lines.join("\n").length;
|
|
5110
|
+
const total = totalChars ?? held;
|
|
5074
5111
|
if (cut) return {
|
|
5075
5112
|
shown,
|
|
5076
|
-
more: `… +${(
|
|
5113
|
+
more: `… +${(total - chars).toLocaleString()} chars`
|
|
5114
|
+
};
|
|
5115
|
+
if (totalChars !== void 0 && total > held) return {
|
|
5116
|
+
shown,
|
|
5117
|
+
more: `… +${(total - chars).toLocaleString()} chars`
|
|
5077
5118
|
};
|
|
5078
5119
|
const hidden = lines.length - shown.length;
|
|
5079
5120
|
return {
|
|
@@ -5082,6 +5123,157 @@ function collapsedResult(lines) {
|
|
|
5082
5123
|
};
|
|
5083
5124
|
}
|
|
5084
5125
|
//#endregion
|
|
5126
|
+
//#region src/components/agent/tool-result-fetch.tsx
|
|
5127
|
+
const FetchContext = createContext(async () => false);
|
|
5128
|
+
function ToolResultFetchProvider({ value, children }) {
|
|
5129
|
+
return /* @__PURE__ */ jsx(FetchContext.Provider, {
|
|
5130
|
+
value: value ?? noop$1,
|
|
5131
|
+
children
|
|
5132
|
+
});
|
|
5133
|
+
}
|
|
5134
|
+
const noop$1 = async () => false;
|
|
5135
|
+
function useToolResultFetcher() {
|
|
5136
|
+
return useContext(FetchContext);
|
|
5137
|
+
}
|
|
5138
|
+
//#endregion
|
|
5139
|
+
//#region src/components/agent/tool-result-image.tsx
|
|
5140
|
+
const noop = async () => void 0;
|
|
5141
|
+
const ImageContext = createContext(noop);
|
|
5142
|
+
function ToolResultImageProvider({ value, children }) {
|
|
5143
|
+
return /* @__PURE__ */ jsx(ImageContext.Provider, {
|
|
5144
|
+
value: value ?? noop,
|
|
5145
|
+
children
|
|
5146
|
+
});
|
|
5147
|
+
}
|
|
5148
|
+
function useToolResultImageLoader() {
|
|
5149
|
+
return useContext(ImageContext);
|
|
5150
|
+
}
|
|
5151
|
+
/**
|
|
5152
|
+
* Long enough that a fast scrub through an image-heavy session fetches nothing
|
|
5153
|
+
* it flew past, short enough to be invisible to a reader who stopped.
|
|
5154
|
+
*
|
|
5155
|
+
* There is no second visibility system here on purpose: the transcript is
|
|
5156
|
+
* virtualized, so a *mounted* row is by definition within an overscan of the
|
|
5157
|
+
* viewport — the virtualizer already is the IntersectionObserver, and a second
|
|
5158
|
+
* answer to a question that has one is how the two disagree.
|
|
5159
|
+
*/
|
|
5160
|
+
const MOUNT_SETTLE_MS = 150;
|
|
5161
|
+
/**
|
|
5162
|
+
* One box's load, for either theme.
|
|
5163
|
+
*
|
|
5164
|
+
* Fires once the row has been mounted for {@link MOUNT_SETTLE_MS}, and then
|
|
5165
|
+
* **runs to completion** — an aborted fetch re-pays the whole image on the
|
|
5166
|
+
* return visit, and the gateway is HTTP/1.1, so the browser's per-origin
|
|
5167
|
+
* connection cap is the concurrency throttle for free.
|
|
5168
|
+
*
|
|
5169
|
+
* The effect keys on the address's *primitives*, never on the ref object: the
|
|
5170
|
+
* reducer replaces items on every streamed delta, so an object-identity dep
|
|
5171
|
+
* would re-run this on every token of the turn after it.
|
|
5172
|
+
*/
|
|
5173
|
+
function useToolResultImageSrc(ref) {
|
|
5174
|
+
const load = useToolResultImageLoader();
|
|
5175
|
+
const [state, setState] = useState({ failed: false });
|
|
5176
|
+
const { toolUseId, sourceSeq, partIndex, mediaType, bytes } = ref;
|
|
5177
|
+
useEffect(() => {
|
|
5178
|
+
let live = true;
|
|
5179
|
+
setState({ failed: false });
|
|
5180
|
+
const timer = setTimeout(() => {
|
|
5181
|
+
load({
|
|
5182
|
+
toolUseId,
|
|
5183
|
+
sourceSeq,
|
|
5184
|
+
partIndex,
|
|
5185
|
+
mediaType,
|
|
5186
|
+
bytes
|
|
5187
|
+
}).then((src) => {
|
|
5188
|
+
if (live) setState({
|
|
5189
|
+
src,
|
|
5190
|
+
failed: src === void 0
|
|
5191
|
+
});
|
|
5192
|
+
}).catch(() => {
|
|
5193
|
+
if (live) setState({ failed: true });
|
|
5194
|
+
});
|
|
5195
|
+
}, MOUNT_SETTLE_MS);
|
|
5196
|
+
return () => {
|
|
5197
|
+
live = false;
|
|
5198
|
+
clearTimeout(timer);
|
|
5199
|
+
};
|
|
5200
|
+
}, [
|
|
5201
|
+
load,
|
|
5202
|
+
toolUseId,
|
|
5203
|
+
sourceSeq,
|
|
5204
|
+
partIndex,
|
|
5205
|
+
mediaType,
|
|
5206
|
+
bytes
|
|
5207
|
+
]);
|
|
5208
|
+
return state;
|
|
5209
|
+
}
|
|
5210
|
+
/** ~64 MB of decoded pictures held at once. At the corpus's 335 KB median that
|
|
5211
|
+
* is ~190 images, which no viewport holds; the budget exists so a session
|
|
5212
|
+
* scrolled end to end does not pin every screenshot it passed. */
|
|
5213
|
+
const CACHE_BUDGET_BYTES = 64 * 1024 * 1024;
|
|
5214
|
+
/**
|
|
5215
|
+
* `useHostImage`'s shape, generalized to the replay route — and **bounded**,
|
|
5216
|
+
* which `useHostImage` is not.
|
|
5217
|
+
*
|
|
5218
|
+
* The promise-per-key cache is what makes this callable from a transcript row at
|
|
5219
|
+
* all: rows re-render on every streamed delta, and an uncached resolver would
|
|
5220
|
+
* re-fetch each time. The LRU is the part that is new. Object URLs pin their
|
|
5221
|
+
* blob until revoked, so a fully-scrolled hundred-image session would otherwise
|
|
5222
|
+
* hold ~50 MB until the panel unmounted — and evicting means revoking, or the
|
|
5223
|
+
* eviction frees a `Map` entry and nothing else.
|
|
5224
|
+
*
|
|
5225
|
+
* Re-fetching on a return scroll is fine, and is the whole design: the bytes are
|
|
5226
|
+
* one authenticated request away, which is precisely what makes it cheap not to
|
|
5227
|
+
* have shipped them in the attach.
|
|
5228
|
+
*/
|
|
5229
|
+
function useToolResultImages(client, sessionId) {
|
|
5230
|
+
const cache = useRef(/* @__PURE__ */ new Map());
|
|
5231
|
+
useEffect(() => () => {
|
|
5232
|
+
for (const entry of cache.current.values()) if (entry.url) URL.revokeObjectURL(entry.url);
|
|
5233
|
+
cache.current.clear();
|
|
5234
|
+
}, []);
|
|
5235
|
+
return useCallback((ref) => {
|
|
5236
|
+
if (!sessionId) return Promise.resolve(void 0);
|
|
5237
|
+
const key = `${sessionId}:${ref.sourceSeq}:${ref.toolUseId}:${ref.partIndex}`;
|
|
5238
|
+
const hit = cache.current.get(key);
|
|
5239
|
+
if (hit) {
|
|
5240
|
+
cache.current.delete(key);
|
|
5241
|
+
cache.current.set(key, hit);
|
|
5242
|
+
return hit.pending;
|
|
5243
|
+
}
|
|
5244
|
+
const pending = client.toolResultImage(sessionId, ref.sourceSeq, ref.toolUseId, ref.partIndex).then((blob) => {
|
|
5245
|
+
if (blob.size === 0) return void 0;
|
|
5246
|
+
const url = URL.createObjectURL(blob);
|
|
5247
|
+
const entry = cache.current.get(key);
|
|
5248
|
+
if (entry) {
|
|
5249
|
+
entry.url = url;
|
|
5250
|
+
entry.bytes = blob.size;
|
|
5251
|
+
evict(cache.current, key);
|
|
5252
|
+
} else URL.revokeObjectURL(url);
|
|
5253
|
+
return url;
|
|
5254
|
+
}).catch(() => void 0);
|
|
5255
|
+
cache.current.set(key, {
|
|
5256
|
+
pending,
|
|
5257
|
+
bytes: ref.bytes
|
|
5258
|
+
});
|
|
5259
|
+
return pending;
|
|
5260
|
+
}, [client, sessionId]);
|
|
5261
|
+
}
|
|
5262
|
+
/** Drop oldest-first until the held bytes fit the budget, revoking as it goes.
|
|
5263
|
+
* `keep` is the entry just resolved — evicting the picture a row is about to
|
|
5264
|
+
* draw would be a fetch spent on nothing. */
|
|
5265
|
+
function evict(cache, keep) {
|
|
5266
|
+
let held = 0;
|
|
5267
|
+
for (const entry of cache.values()) held += entry.bytes;
|
|
5268
|
+
for (const [key, entry] of cache) {
|
|
5269
|
+
if (held <= CACHE_BUDGET_BYTES) return;
|
|
5270
|
+
if (key === keep) continue;
|
|
5271
|
+
if (entry.url) URL.revokeObjectURL(entry.url);
|
|
5272
|
+
cache.delete(key);
|
|
5273
|
+
held -= entry.bytes;
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
//#endregion
|
|
5085
5277
|
//#region src/components/terminal/tool-run.ts
|
|
5086
5278
|
/**
|
|
5087
5279
|
* What breaks a run.
|
|
@@ -5138,8 +5330,238 @@ function runSummary(items, busy) {
|
|
|
5138
5330
|
const breakdown = [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([family, count]) => `${count} ${family}`).join(", ");
|
|
5139
5331
|
return `${verb}${n} tool${n === 1 ? "" : "s"} · ${breakdown}${tail}`;
|
|
5140
5332
|
}
|
|
5333
|
+
const clip = (text, max = 80) => text.length > max ? text.slice(0, max - 1) + "…" : text;
|
|
5334
|
+
const trimmed = (value) => typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
5335
|
+
/**
|
|
5336
|
+
* The row's identity half: which task this is.
|
|
5337
|
+
*
|
|
5338
|
+
* The Claude SDK's `Task` input carries `subagent_type` (e.g. "Explore") and a
|
|
5339
|
+
* 3–5 word `description`, and both are worth the line: parallel tasks are the
|
|
5340
|
+
* whole reason the block exists, and two rows both reading `Task(…)` answer
|
|
5341
|
+
* nothing. `Task(Explore · find the auth check)` — falling back to the
|
|
5342
|
+
* ordinary input preview when an engine sends neither, so the header is never
|
|
5343
|
+
* emptier than a plain tool row's.
|
|
5344
|
+
*/
|
|
5345
|
+
function taskLabel(task) {
|
|
5346
|
+
const input = task.input;
|
|
5347
|
+
const description = trimmed(input?.description);
|
|
5348
|
+
const agent = trimmed(input?.subagent_type);
|
|
5349
|
+
const inner = agent && description ? `${agent} · ${clip(description)}` : agent ?? (description ? clip(description) : toolInputPreview(task.input));
|
|
5350
|
+
return `${task.name}(${inner})`;
|
|
5351
|
+
}
|
|
5352
|
+
const callBusy = (call) => call.status === "running" || call.status === "pending";
|
|
5353
|
+
/** Did this one call fail? Both spellings are needed: an out-of-loop execution
|
|
5354
|
+
* failure sets `status` with no `is_error` block to read, and an engine can flag
|
|
5355
|
+
* `is_error` on a call the reducer has not settled yet. */
|
|
5356
|
+
const callFailed = (call) => call.status === "failed" || call.result?.isError === true;
|
|
5357
|
+
/**
|
|
5358
|
+
* Does a folded run colour red? **Only when its last call failed.**
|
|
5359
|
+
*
|
|
5360
|
+
* It used to be `some`, on the argument that a failure should colour the block
|
|
5361
|
+
* rather than fragment it. The argument was right about not fragmenting and
|
|
5362
|
+
* wrong about `some`: a run is a sequence the model worked through, and a
|
|
5363
|
+
* failure it recovered from two calls later is how work goes — a grep that
|
|
5364
|
+
* matched nothing, a build fixed on the second go. Reddening the whole run for
|
|
5365
|
+
* it means a normal working session is painted red, which spends the colour
|
|
5366
|
+
* that should have been left for the one thing still broken.
|
|
5367
|
+
*
|
|
5368
|
+
* The last call is the run's *outcome*, and an outcome is what a collapsed row
|
|
5369
|
+
* can honestly claim. The failures inside it are not hidden — they are one
|
|
5370
|
+
* press away, each red on its own row, and the recap counts every one. The
|
|
5371
|
+
* **scrubber agrees with this rule** rather than overriding it: it marks a
|
|
5372
|
+
* failed call only when the call is its row's outcome, which for a run is
|
|
5373
|
+
* exactly this one. It used to mark every member on the argument that the
|
|
5374
|
+
* rail asks a different question; against a real session that was nine alarms
|
|
5375
|
+
* on the rail for a transcript reddening one row.
|
|
5376
|
+
*/
|
|
5377
|
+
function runFailed(items) {
|
|
5378
|
+
const last = items[items.length - 1];
|
|
5379
|
+
return last !== void 0 && callFailed(last);
|
|
5380
|
+
}
|
|
5381
|
+
/** Is anything inside still going? The call itself, normally — the Task
|
|
5382
|
+
* settles only when its subagent finishes — but a bridged or deferred child
|
|
5383
|
+
* can outlive it, and a pulse that stopped while a child still worked would
|
|
5384
|
+
* read as a hang. */
|
|
5385
|
+
function taskBusy(task, children) {
|
|
5386
|
+
return callBusy(task) || children.some((child) => child.kind === "tool_call" && callBusy(child));
|
|
5387
|
+
}
|
|
5388
|
+
/**
|
|
5389
|
+
* Does the row colour red? **The task's own outcome, and nothing else.**
|
|
5390
|
+
*
|
|
5391
|
+
* It used to be "or any child call's", which does not survive contact with a
|
|
5392
|
+
* real subagent: an agent that ran a hundred calls, one of them a grep that
|
|
5393
|
+
* matched nothing, came back with a red line saying it had failed. It had not —
|
|
5394
|
+
* it had done exactly what it was asked, and the transcript said otherwise in
|
|
5395
|
+
* the one colour reserved for things that need a human.
|
|
5396
|
+
*
|
|
5397
|
+
* This is the call `SubagentInfo.status` already makes, and it made it for this
|
|
5398
|
+
* reason (see `packages/protocol`): the sub-agent's **own** `tool_result`
|
|
5399
|
+
* `is_error`, deliberately not `taskFailed`. The argument there was that a
|
|
5400
|
+
* nothing-matched grep must not read as a failed run *beside a session name*;
|
|
5401
|
+
* what a hundred-call agent shows is that it must not read that way beside the
|
|
5402
|
+
* `Task` row either. Two surfaces, one rule, one spelling.
|
|
5403
|
+
*
|
|
5404
|
+
* Nothing is concealed by this. A failed child is red on its own row, one press
|
|
5405
|
+
* away, and the recap counts it. The **scrubber follows this rule too** and no
|
|
5406
|
+
* longer marks such a child: a red tick on the rail says precisely what this
|
|
5407
|
+
* row is forbidden from saying. The sub-agent band still says an agent ran
|
|
5408
|
+
* here, and the task's own red tick still says it came back broken — which is
|
|
5409
|
+
* what the two channels are for.
|
|
5410
|
+
*/
|
|
5411
|
+
function taskFailed(task) {
|
|
5412
|
+
return callFailed(task);
|
|
5413
|
+
}
|
|
5414
|
+
/**
|
|
5415
|
+
* The collapsed task row's one line: identity, then scale.
|
|
5416
|
+
*
|
|
5417
|
+
* `Task(Explore · find the auth check) · 7 tools…` while the subagent works —
|
|
5418
|
+
* the count grows as it does, which is the row's progress reading, and the
|
|
5419
|
+
* trailing ellipsis is the same in-flight signal `runSummary` uses (the pulse
|
|
5420
|
+
* in the gutter carries the beat). Settled, the ellipsis drops:
|
|
5421
|
+
* `… · 7 tools`. "Tools" and not "tool calls" because `runSummary` already
|
|
5422
|
+
* chose that word for the same count one row over.
|
|
5423
|
+
*
|
|
5424
|
+
* The counts are counted from the absorbed children, never read from the
|
|
5425
|
+
* engine's structured Task output — WorkerDeck does not plumb structured tool
|
|
5426
|
+
* results to clients, so a transcript replayed tomorrow must spell the same
|
|
5427
|
+
* line from the same items it holds today.
|
|
5428
|
+
*
|
|
5429
|
+
* With no tool calls yet — the subagent thinking, or only its brief arrived —
|
|
5430
|
+
* the line says `working…`, because `0 tools…` reads as a stall; settled with
|
|
5431
|
+
* none it says `done`.
|
|
5432
|
+
*/
|
|
5433
|
+
function taskSummary(task, children) {
|
|
5434
|
+
const busy = taskBusy(task, children);
|
|
5435
|
+
const calls = children.reduce((n, child) => n + (child.kind === "tool_call" ? 1 : 0), 0);
|
|
5436
|
+
const label = taskLabel(task);
|
|
5437
|
+
if (calls === 0) return busy ? `${label} · working…` : `${label} · done`;
|
|
5438
|
+
return `${label} · ${calls} tool${calls === 1 ? "" : "s"}${busy ? "…" : ""}`;
|
|
5439
|
+
}
|
|
5440
|
+
//#endregion
|
|
5441
|
+
//#region src/components/terminal/blocks.ts
|
|
5442
|
+
/**
|
|
5443
|
+
* The id of the tool call this item was produced inside, or `undefined` at the
|
|
5444
|
+
* top level. One spelling for both shapes the reducer emits: `assistant_text`
|
|
5445
|
+
* / `thinking` / `tool_call` carry `parentToolUseId: string | null` on every
|
|
5446
|
+
* instance, while `user` carries it **optionally** (a human prompt has no
|
|
5447
|
+
* parent at all — the key exists only on a subagent's brief). Callers must go
|
|
5448
|
+
* through this rather than reading the field, or the absent-key case silently
|
|
5449
|
+
* types as a compile error on one kind and a miss on another.
|
|
5450
|
+
*/
|
|
5451
|
+
function parentOf(item) {
|
|
5452
|
+
return ("parentToolUseId" in item ? item.parentToolUseId : void 0) ?? void 0;
|
|
5453
|
+
}
|
|
5454
|
+
/** Is this a row the transcript folds into a run? Any tool call is — see
|
|
5455
|
+
* `tool-run.ts` for why this is no longer shell-only. */
|
|
5456
|
+
function isRunCall(item) {
|
|
5457
|
+
return item.kind === "tool_call";
|
|
5458
|
+
}
|
|
5459
|
+
/** The absorbed items, flat and in stream order — what `taskSummary` counts
|
|
5460
|
+
* and the collapsed row's one line is built from. */
|
|
5461
|
+
function taskChildItems(block) {
|
|
5462
|
+
return block.children.flatMap((child) => "run" in child ? child.run : [child.item]);
|
|
5463
|
+
}
|
|
5464
|
+
/** Append one item to a leaf-block list, folding it into the previous run when
|
|
5465
|
+
* the membership rule allows — the one fold implementation, used for the
|
|
5466
|
+
* top-level stream and for each task's children alike. */
|
|
5467
|
+
function pushLeaf(out, item, index) {
|
|
5468
|
+
const previous = out.at(-1);
|
|
5469
|
+
if (isRunCall(item)) {
|
|
5470
|
+
if (previous && "run" in previous && foldsTogether(previous.run[0], item)) {
|
|
5471
|
+
previous.run.push(item);
|
|
5472
|
+
previous.indices.push(index);
|
|
5473
|
+
} else out.push({
|
|
5474
|
+
key: `run:${item.id}`,
|
|
5475
|
+
run: [item],
|
|
5476
|
+
indices: [index],
|
|
5477
|
+
index
|
|
5478
|
+
});
|
|
5479
|
+
return;
|
|
5480
|
+
}
|
|
5481
|
+
out.push({
|
|
5482
|
+
key: `${item.kind}:${item.id}`,
|
|
5483
|
+
item,
|
|
5484
|
+
index
|
|
5485
|
+
});
|
|
5486
|
+
}
|
|
5487
|
+
/**
|
|
5488
|
+
* @param offset What `items[0]`'s index is in the whole transcript — the
|
|
5489
|
+
* virtualized shell folds each side of the recap boundary separately, and the
|
|
5490
|
+
* rows still have to say where they sit for the catch-up dimming.
|
|
5491
|
+
* @param fold Whether to group at all. `false` gives one block per item —
|
|
5492
|
+
* no runs *and no task absorption* — which is what the cards variant
|
|
5493
|
+
* renders: this is the terminal theme's rule and must not silently reshape
|
|
5494
|
+
* another renderer's row list.
|
|
5495
|
+
*/
|
|
5496
|
+
function terminalBlocks(items, offset = 0, fold = true) {
|
|
5497
|
+
if (!fold) return items.map((item, position) => ({
|
|
5498
|
+
key: `${item.kind}:${item.id}`,
|
|
5499
|
+
item,
|
|
5500
|
+
index: offset + position
|
|
5501
|
+
}));
|
|
5502
|
+
const topLevelCalls = /* @__PURE__ */ new Set();
|
|
5503
|
+
for (const item of items) if (item.kind === "tool_call" && parentOf(item) === void 0) topLevelCalls.add(item.id);
|
|
5504
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
5505
|
+
items.forEach((item, position) => {
|
|
5506
|
+
const parent = parentOf(item);
|
|
5507
|
+
if (parent !== void 0 && topLevelCalls.has(parent)) {
|
|
5508
|
+
const list = childrenOf.get(parent);
|
|
5509
|
+
if (list) list.push({
|
|
5510
|
+
item,
|
|
5511
|
+
index: offset + position
|
|
5512
|
+
});
|
|
5513
|
+
else childrenOf.set(parent, [{
|
|
5514
|
+
item,
|
|
5515
|
+
index: offset + position
|
|
5516
|
+
}]);
|
|
5517
|
+
}
|
|
5518
|
+
});
|
|
5519
|
+
const out = [];
|
|
5520
|
+
for (const [position, item] of items.entries()) {
|
|
5521
|
+
const index = offset + position;
|
|
5522
|
+
const parent = parentOf(item);
|
|
5523
|
+
if (parent !== void 0 && childrenOf.has(parent)) continue;
|
|
5524
|
+
if (item.kind === "tool_call") {
|
|
5525
|
+
const children = childrenOf.get(item.id);
|
|
5526
|
+
if (children) {
|
|
5527
|
+
const folded = [];
|
|
5528
|
+
for (const child of children) pushLeaf(folded, child.item, child.index);
|
|
5529
|
+
out.push({
|
|
5530
|
+
key: `task:${item.id}`,
|
|
5531
|
+
task: item,
|
|
5532
|
+
children: folded,
|
|
5533
|
+
childIndices: children.map((child) => child.index),
|
|
5534
|
+
index
|
|
5535
|
+
});
|
|
5536
|
+
continue;
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
pushLeaf(out, item, index);
|
|
5540
|
+
}
|
|
5541
|
+
return out;
|
|
5542
|
+
}
|
|
5543
|
+
/** Spacing between two items: a blank line, unless the pair belongs together.
|
|
5544
|
+
* Tool output already sits under its call, and a run of tool calls reads as one
|
|
5545
|
+
* block — the CLI leaves no blank line inside either. */
|
|
5546
|
+
function needsBlank(previous, next) {
|
|
5547
|
+
if (previous.kind === "tool_call" && next.kind === "tool_call") return false;
|
|
5548
|
+
return true;
|
|
5549
|
+
}
|
|
5550
|
+
/** The same rule over blocks: a run counts as the tool calls it folded, and a
|
|
5551
|
+
* task block counts as the `Task` call it stands for — a collapsed task row
|
|
5552
|
+
* sits flush with the tool rows of the same turn, exactly as the call itself
|
|
5553
|
+
* did before it grew children. */
|
|
5554
|
+
function blockNeedsBlank(previous, next) {
|
|
5555
|
+
const kind = (block) => "item" in block ? block.item.kind : "tool_call";
|
|
5556
|
+
return !(kind(previous) === "tool_call" && kind(next) === "tool_call");
|
|
5557
|
+
}
|
|
5141
5558
|
/** How much the expanded row shows before offering the rest. The collapsed
|
|
5142
|
-
* budget is `collapsedResult`'s, shared with the height calculator.
|
|
5559
|
+
* budget is `collapsedResult`'s, shared with the height calculator.
|
|
5560
|
+
*
|
|
5561
|
+
* Exported for one test and not from the package: protocol's
|
|
5562
|
+
* `TOOL_RESULT_HEAD_CHARS` is chosen to exceed it, so that a truncated result's
|
|
5563
|
+
* open state is byte-identical to an untruncated one and only the uncapped
|
|
5564
|
+
* `full` press ever fetches. That relationship is asserted, not assumed. */
|
|
5143
5565
|
const RESULT_PREVIEW_CHARS$1 = 2e3;
|
|
5144
5566
|
/** Whole lines up to a character budget — never zero, because a single line
|
|
5145
5567
|
* longer than the budget still has to be shown or the row would open onto
|
|
@@ -5209,6 +5631,8 @@ const TOOL_TONE = {
|
|
|
5209
5631
|
function ToolRow({ item }) {
|
|
5210
5632
|
const [open, setOpen] = useState(false);
|
|
5211
5633
|
const [full, setFull] = useState(false);
|
|
5634
|
+
const [fetching, setFetching] = useState(false);
|
|
5635
|
+
const fetchResult = useToolResultFetcher();
|
|
5212
5636
|
const reveal = useRevealOnOpen(open);
|
|
5213
5637
|
const status = item.status ?? (item.result === void 0 ? "running" : "settled");
|
|
5214
5638
|
const busy = status === "running" || status === "pending";
|
|
@@ -5216,10 +5640,12 @@ function ToolRow({ item }) {
|
|
|
5216
5640
|
const pulse = usePulse(busy);
|
|
5217
5641
|
const text = item.result?.text ?? "";
|
|
5218
5642
|
const lines = text.trimEnd().split("\n");
|
|
5219
|
-
const collapsed = collapsedResult(lines);
|
|
5643
|
+
const collapsed = collapsedResult(lines, item.result?.totalChars);
|
|
5220
5644
|
const preview = open ? full ? lines : clipToChars(lines, RESULT_PREVIEW_CHARS$1) : collapsed.shown;
|
|
5221
5645
|
const hidden = lines.length - preview.length;
|
|
5222
5646
|
const clipped = open && !full && hidden > 0;
|
|
5647
|
+
const truncated = item.result?.truncated === true;
|
|
5648
|
+
const missing = truncated ? (item.result?.totalChars ?? 0) - text.length : 0;
|
|
5223
5649
|
const tone = isError ? "red" : status === "settled" && isMutatingTool(item.name) ? "green" : TOOL_TONE[status] ?? "dim";
|
|
5224
5650
|
const command = item.input?.command;
|
|
5225
5651
|
const copyable = typeof command === "string" ? command : text;
|
|
@@ -5231,76 +5657,123 @@ function ToolRow({ item }) {
|
|
|
5231
5657
|
text: copyable,
|
|
5232
5658
|
label: "Copy"
|
|
5233
5659
|
}) : 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
|
-
|
|
5660
|
+
children: [
|
|
5661
|
+
/* @__PURE__ */ jsx(Pressable, {
|
|
5662
|
+
onPress: () => setOpen((v) => !v),
|
|
5663
|
+
expanded: open,
|
|
5664
|
+
children: /* @__PURE__ */ jsxs(Row, {
|
|
5665
|
+
glyph: busy ? pulse : "●",
|
|
5666
|
+
glyphTone: tone,
|
|
5667
|
+
tone: "fg",
|
|
5668
|
+
children: [
|
|
5669
|
+
/* @__PURE__ */ jsx(Ink, {
|
|
5670
|
+
bold: true,
|
|
5671
|
+
tone: "bright",
|
|
5672
|
+
children: item.name
|
|
5673
|
+
}),
|
|
5674
|
+
/* @__PURE__ */ jsxs(Ink, {
|
|
5675
|
+
tone: "dim",
|
|
5676
|
+
children: [
|
|
5677
|
+
"(",
|
|
5678
|
+
toolInputPreview(item.input),
|
|
5679
|
+
")"
|
|
5680
|
+
]
|
|
5681
|
+
}),
|
|
5682
|
+
item.backend && item.backend !== "server" ? /* @__PURE__ */ jsxs(Ink, {
|
|
5683
|
+
tone: "faint",
|
|
5684
|
+
children: [" · ", item.backend]
|
|
5685
|
+
}) : null
|
|
5686
|
+
]
|
|
5687
|
+
})
|
|
5688
|
+
}),
|
|
5689
|
+
item.result?.images?.map((image) => /* @__PURE__ */ jsx(TerminalImage, {
|
|
5690
|
+
toolUseId: item.id,
|
|
5691
|
+
image
|
|
5692
|
+
}, image.partIndex)),
|
|
5693
|
+
item.patch && !open ? /* @__PURE__ */ jsx(TerminalDiff, { patch: item.patch }) : text ? /* @__PURE__ */ jsxs(Fragment$1, { children: [preview.map((line, index) => /* @__PURE__ */ jsx(Row, {
|
|
5694
|
+
indent: 1,
|
|
5695
|
+
columns: 3,
|
|
5696
|
+
glyph: index === 0 ? "⎿" : void 0,
|
|
5697
|
+
tone: isError ? "red" : "dim",
|
|
5698
|
+
children: line || "\xA0"
|
|
5699
|
+
}, index)), !open ? collapsed.more ? /* @__PURE__ */ jsx(Row, {
|
|
5700
|
+
indent: 1,
|
|
5701
|
+
columns: 3,
|
|
5702
|
+
tone: "faint",
|
|
5703
|
+
children: collapsed.more
|
|
5704
|
+
}) : null : clipped || truncated ? /* @__PURE__ */ jsx(Row, {
|
|
5705
|
+
indent: 1,
|
|
5706
|
+
columns: 3,
|
|
5707
|
+
tone: "faint",
|
|
5708
|
+
children: fetching ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
5709
|
+
"… fetching ",
|
|
5710
|
+
(item.result?.totalChars ?? 0).toLocaleString(),
|
|
5711
|
+
" chars"
|
|
5712
|
+
] }) : clipped || truncated ? /* @__PURE__ */ jsx("button", {
|
|
5713
|
+
type: "button",
|
|
5714
|
+
className: "term-press term-link",
|
|
5715
|
+
onClick: () => {
|
|
5716
|
+
setFull(true);
|
|
5717
|
+
if (!truncated) return;
|
|
5718
|
+
setFetching(true);
|
|
5719
|
+
fetchResult(item.id).finally(() => setFetching(false));
|
|
5720
|
+
},
|
|
5721
|
+
children: truncated ? `… +${missing.toLocaleString()} chars — fetch the rest` : `… +${hidden} line${hidden === 1 ? "" : "s"} — show all ${text.length.toLocaleString()} chars`
|
|
5722
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
5723
|
+
"… +",
|
|
5724
|
+
hidden,
|
|
5725
|
+
" line",
|
|
5726
|
+
hidden === 1 ? "" : "s"
|
|
5727
|
+
] })
|
|
5728
|
+
}) : hidden > 0 ? /* @__PURE__ */ jsxs(Row, {
|
|
5729
|
+
indent: 1,
|
|
5730
|
+
columns: 3,
|
|
5731
|
+
tone: "faint",
|
|
5280
5732
|
children: [
|
|
5281
5733
|
"… +",
|
|
5282
5734
|
hidden,
|
|
5283
5735
|
" line",
|
|
5284
|
-
hidden === 1 ? "" : "s"
|
|
5285
|
-
" — show all",
|
|
5286
|
-
" ",
|
|
5287
|
-
text.length.toLocaleString(),
|
|
5288
|
-
" chars"
|
|
5736
|
+
hidden === 1 ? "" : "s"
|
|
5289
5737
|
]
|
|
5290
|
-
}) :
|
|
5291
|
-
|
|
5292
|
-
hidden,
|
|
5293
|
-
" line",
|
|
5294
|
-
hidden === 1 ? "" : "s"
|
|
5295
|
-
] })
|
|
5296
|
-
}) : null] }) : null]
|
|
5738
|
+
}) : null] }) : null
|
|
5739
|
+
]
|
|
5297
5740
|
})
|
|
5298
5741
|
});
|
|
5299
5742
|
}
|
|
5300
|
-
/**
|
|
5301
|
-
*
|
|
5302
|
-
|
|
5303
|
-
|
|
5743
|
+
/**
|
|
5744
|
+
* A picture a tool returned, in a box of {@link IMAGE_BOX_LINES} whole lines.
|
|
5745
|
+
*
|
|
5746
|
+
* **Three states, one height.** Before the fetch lands the box is a wash and the
|
|
5747
|
+
* size the gateway declared; after it, the picture, letterboxed inside the same
|
|
5748
|
+
* box; on a refusal, `image unavailable` in it. Nothing here may ever collapse
|
|
5749
|
+
* to nothing — that is `HostImage`'s return-null-then-pop, which in a
|
|
5750
|
+
* *virtualized* list is not a flicker but a reflow of every row below it, and
|
|
5751
|
+
* the height calculator would have been lying about the row from plan time.
|
|
5752
|
+
*
|
|
5753
|
+
* The box is why the calculator can stay exact: it is a constant, not a function
|
|
5754
|
+
* of pixels nobody has downloaded yet.
|
|
5755
|
+
*/
|
|
5756
|
+
function TerminalImage({ toolUseId, image }) {
|
|
5757
|
+
const { src, failed } = useToolResultImageSrc({
|
|
5758
|
+
toolUseId,
|
|
5759
|
+
...image
|
|
5760
|
+
});
|
|
5761
|
+
return /* @__PURE__ */ jsx(Row, {
|
|
5762
|
+
indent: 1,
|
|
5763
|
+
columns: 3,
|
|
5764
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
5765
|
+
className: "term-image",
|
|
5766
|
+
"data-state": src ? "loaded" : failed ? "failed" : "pending",
|
|
5767
|
+
style: { height: `calc(var(--term-line) * 12)` },
|
|
5768
|
+
children: src ? /* @__PURE__ */ jsx("img", {
|
|
5769
|
+
src,
|
|
5770
|
+
alt: imagePlaceholder(image)
|
|
5771
|
+
}) : /* @__PURE__ */ jsx(Ink, {
|
|
5772
|
+
tone: "faint",
|
|
5773
|
+
children: failed ? IMAGE_UNAVAILABLE : imagePlaceholder(image)
|
|
5774
|
+
})
|
|
5775
|
+
})
|
|
5776
|
+
});
|
|
5304
5777
|
}
|
|
5305
5778
|
/**
|
|
5306
5779
|
* A run of tool calls, as one line.
|
|
@@ -5312,10 +5785,11 @@ function isRunCall(item) {
|
|
|
5312
5785
|
* collapses to its count and gets out of the way — and opens, in full, the
|
|
5313
5786
|
* moment it is the thing you actually want.
|
|
5314
5787
|
*
|
|
5315
|
-
* The membership and
|
|
5316
|
-
* calculator. A
|
|
5317
|
-
*
|
|
5318
|
-
* the run
|
|
5788
|
+
* The membership, wording and failure rules live in `tool-run.ts`, shared with
|
|
5789
|
+
* the height calculator. A failure never breaks the run — fragmenting it around
|
|
5790
|
+
* one would hide the failure in a longer list rather than surface it — but only
|
|
5791
|
+
* the run's **last** call colours it, because that is the run's outcome and an
|
|
5792
|
+
* outcome is what a collapsed row can honestly claim (see `runFailed`).
|
|
5319
5793
|
*/
|
|
5320
5794
|
function ToolRunRow({ items }) {
|
|
5321
5795
|
const [open, setOpen] = useState(false);
|
|
@@ -5324,7 +5798,7 @@ function ToolRunRow({ items }) {
|
|
|
5324
5798
|
const status = item.status ?? (item.result === void 0 ? "running" : "settled");
|
|
5325
5799
|
return status === "running" || status === "pending";
|
|
5326
5800
|
});
|
|
5327
|
-
const failed = items
|
|
5801
|
+
const failed = runFailed(items);
|
|
5328
5802
|
const pulse = usePulse(busy);
|
|
5329
5803
|
return /* @__PURE__ */ jsxs("div", {
|
|
5330
5804
|
ref: reveal,
|
|
@@ -5341,36 +5815,6 @@ function ToolRunRow({ items }) {
|
|
|
5341
5815
|
}), open ? /* @__PURE__ */ jsx("div", { children: items.map((item) => /* @__PURE__ */ jsx(ToolRow, { item }, item.id)) }) : null]
|
|
5342
5816
|
});
|
|
5343
5817
|
}
|
|
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
5818
|
function TurnResultRow$1({ item }) {
|
|
5375
5819
|
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs(Row, {
|
|
5376
5820
|
tone: item.isError ? "red" : "faint",
|
|
@@ -5456,19 +5900,6 @@ function WorkingRow({ label, startedAt, tokens }) {
|
|
|
5456
5900
|
}) : null]
|
|
5457
5901
|
});
|
|
5458
5902
|
}
|
|
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
5903
|
//#endregion
|
|
5473
5904
|
//#region src/components/terminal/surface.tsx
|
|
5474
5905
|
function TerminalSurface({ fontSize, lineHeight, bleed, affordances, className, style, children, ...props }) {
|
|
@@ -7145,7 +7576,7 @@ function Hint({ children }) {
|
|
|
7145
7576
|
* background gradients rather than a border, so it costs no layout: a 1px border
|
|
7146
7577
|
* would push its contents a pixel off the column every other row sits on.
|
|
7147
7578
|
*/
|
|
7148
|
-
function Box({ children, className }) {
|
|
7579
|
+
function Box$1({ children, className }) {
|
|
7149
7580
|
return /* @__PURE__ */ jsx("div", {
|
|
7150
7581
|
className: cn("term-box", className),
|
|
7151
7582
|
children
|
|
@@ -7522,7 +7953,7 @@ function QuestionStep({ question, selection, cursor, onCursor, onToggle, onOther
|
|
|
7522
7953
|
checked: multiSelect ? selected : void 0,
|
|
7523
7954
|
marker: "check",
|
|
7524
7955
|
selected: !multiSelect && selected,
|
|
7525
|
-
detail: option.preview && cursor === index ? /* @__PURE__ */ jsx(Box, { children: option.preview.split("\n").map((line, row) => /* @__PURE__ */ jsx(Row, {
|
|
7956
|
+
detail: option.preview && cursor === index ? /* @__PURE__ */ jsx(Box$1, { children: option.preview.split("\n").map((line, row) => /* @__PURE__ */ jsx(Row, {
|
|
7526
7957
|
columns: 0,
|
|
7527
7958
|
tone: "dim",
|
|
7528
7959
|
children: line || " "
|
|
@@ -8387,14 +8818,18 @@ function resultLanguage(item) {
|
|
|
8387
8818
|
function ToolCallCard({ item, hostImage, className }) {
|
|
8388
8819
|
const [open, setOpen] = useState(false);
|
|
8389
8820
|
const [fullResult, setFullResult] = useState(false);
|
|
8821
|
+
const [fetching, setFetching] = useState(false);
|
|
8822
|
+
const fetchResult = useToolResultFetcher();
|
|
8390
8823
|
const imagePath = imagePathOf(item);
|
|
8391
8824
|
const status = item.status ?? (item.result === void 0 ? "running" : "settled");
|
|
8392
8825
|
const badge = STATE_BADGE[status];
|
|
8393
8826
|
const isError = status === "failed" || item.result?.isError === true;
|
|
8394
8827
|
const Icon = toolIcon(item.name);
|
|
8395
8828
|
const resultText = item.result?.text ?? "";
|
|
8396
|
-
const
|
|
8397
|
-
const shownResult =
|
|
8829
|
+
const clipped = !fullResult && resultText.length > RESULT_PREVIEW_CHARS;
|
|
8830
|
+
const shownResult = clipped ? resultText.slice(0, RESULT_PREVIEW_CHARS) : resultText;
|
|
8831
|
+
const headOnly = item.result?.truncated === true;
|
|
8832
|
+
const totalChars = item.result?.totalChars ?? resultText.length;
|
|
8398
8833
|
const details = open ? /* @__PURE__ */ jsxs("div", {
|
|
8399
8834
|
className: "flex flex-col gap-2 border-t border-border p-2.5",
|
|
8400
8835
|
children: [
|
|
@@ -8412,13 +8847,25 @@ function ToolCallCard({ item, hostImage, className }) {
|
|
|
8412
8847
|
language: resultLanguage(item),
|
|
8413
8848
|
label: isError ? "Error" : "Result",
|
|
8414
8849
|
className: cn(isError && "border-danger/40 [&_pre]:text-danger")
|
|
8415
|
-
}),
|
|
8850
|
+
}), fetching ? /* @__PURE__ */ jsxs("p", {
|
|
8851
|
+
className: "mt-1 text-label text-fg-3",
|
|
8852
|
+
children: [
|
|
8853
|
+
"Fetching ",
|
|
8854
|
+
totalChars.toLocaleString(),
|
|
8855
|
+
" chars…"
|
|
8856
|
+
]
|
|
8857
|
+
}) : clipped || headOnly ? /* @__PURE__ */ jsxs("button", {
|
|
8416
8858
|
type: "button",
|
|
8417
8859
|
className: "mt-1 text-label text-fg-3 underline-offset-2 hover:underline",
|
|
8418
|
-
onClick: () =>
|
|
8860
|
+
onClick: () => {
|
|
8861
|
+
setFullResult(true);
|
|
8862
|
+
if (!headOnly) return;
|
|
8863
|
+
setFetching(true);
|
|
8864
|
+
fetchResult(item.id).finally(() => setFetching(false));
|
|
8865
|
+
},
|
|
8419
8866
|
children: [
|
|
8420
8867
|
"Show all ",
|
|
8421
|
-
|
|
8868
|
+
totalChars.toLocaleString(),
|
|
8422
8869
|
" chars"
|
|
8423
8870
|
]
|
|
8424
8871
|
}) : null] }) : null
|
|
@@ -8428,6 +8875,13 @@ function ToolCallCard({ item, hostImage, className }) {
|
|
|
8428
8875
|
path: imagePath,
|
|
8429
8876
|
load: hostImage
|
|
8430
8877
|
}) : null;
|
|
8878
|
+
const resultImages = item.result?.images?.length ? /* @__PURE__ */ jsx("div", {
|
|
8879
|
+
className: "flex flex-col gap-2 border-t border-border p-2.5",
|
|
8880
|
+
children: item.result.images.map((ref) => /* @__PURE__ */ jsx(ResultImage, {
|
|
8881
|
+
toolUseId: item.id,
|
|
8882
|
+
image: ref
|
|
8883
|
+
}, ref.partIndex))
|
|
8884
|
+
}) : null;
|
|
8431
8885
|
return /* @__PURE__ */ jsxs("div", {
|
|
8432
8886
|
"data-slot": "tool-call",
|
|
8433
8887
|
"data-state": status,
|
|
@@ -8466,6 +8920,7 @@ function ToolCallCard({ item, hostImage, className }) {
|
|
|
8466
8920
|
]
|
|
8467
8921
|
}),
|
|
8468
8922
|
image,
|
|
8923
|
+
resultImages,
|
|
8469
8924
|
details
|
|
8470
8925
|
]
|
|
8471
8926
|
});
|
|
@@ -8481,6 +8936,34 @@ function PlainPayload({ code, label, className }) {
|
|
|
8481
8936
|
});
|
|
8482
8937
|
}
|
|
8483
8938
|
/**
|
|
8939
|
+
* An image part of the result, fetched by reference.
|
|
8940
|
+
*
|
|
8941
|
+
* The frame is a **fixed height in all three states** — placeholder, picture,
|
|
8942
|
+
* failure — which is the one rule this shares with the terminal theme and the
|
|
8943
|
+
* only reason it is worth a component: the transcript is virtualized in both,
|
|
8944
|
+
* and a box that appears when the bytes land shoves every row below it down
|
|
8945
|
+
* while the reader is mid-sentence. Unlike `HostImage`, a failure here is *said*
|
|
8946
|
+
* rather than swallowed: there is no host path in the result text to fall back
|
|
8947
|
+
* on, so silence would be a blank frame with no account of itself.
|
|
8948
|
+
*/
|
|
8949
|
+
function ResultImage({ toolUseId, image }) {
|
|
8950
|
+
const { src, failed } = useToolResultImageSrc({
|
|
8951
|
+
toolUseId,
|
|
8952
|
+
...image
|
|
8953
|
+
});
|
|
8954
|
+
return /* @__PURE__ */ jsx("div", {
|
|
8955
|
+
className: "flex h-60 items-start overflow-hidden rounded-md border border-border bg-surface-hover",
|
|
8956
|
+
children: src ? /* @__PURE__ */ jsx("img", {
|
|
8957
|
+
src,
|
|
8958
|
+
alt: imagePlaceholder(image),
|
|
8959
|
+
className: "h-full max-w-full object-contain"
|
|
8960
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
8961
|
+
className: "p-2 text-label text-fg-4",
|
|
8962
|
+
children: failed ? IMAGE_UNAVAILABLE : imagePlaceholder(image)
|
|
8963
|
+
})
|
|
8964
|
+
});
|
|
8965
|
+
}
|
|
8966
|
+
/**
|
|
8484
8967
|
* A picture that lives on the host, fetched through the gateway's host-file
|
|
8485
8968
|
* route and shown inline.
|
|
8486
8969
|
*
|
|
@@ -8525,7 +9008,7 @@ function createHeightEpoch(width, ch, line) {
|
|
|
8525
9008
|
* The inter-row gap is the *pair's* business (`gapBefore`), not the row's, so
|
|
8526
9009
|
* it is added by the caller. */
|
|
8527
9010
|
function estimateBlockPx(block, epoch) {
|
|
8528
|
-
if ("
|
|
9011
|
+
if (!("item" in block)) return blockHeight(block, epoch).px;
|
|
8529
9012
|
const hit = epoch.cache.get(block.item);
|
|
8530
9013
|
if (hit) return hit.px;
|
|
8531
9014
|
const computed = itemHeight(block.item, epoch);
|
|
@@ -9089,10 +9572,15 @@ function toolRowHeight(item, m, extraPx) {
|
|
|
9089
9572
|
gutterCells: 2,
|
|
9090
9573
|
extraPx
|
|
9091
9574
|
});
|
|
9575
|
+
const images = item.result?.images;
|
|
9576
|
+
if (images?.length) acc = add(acc, {
|
|
9577
|
+
px: images.length * 12 * m.line,
|
|
9578
|
+
exact: true
|
|
9579
|
+
});
|
|
9092
9580
|
if (item.patch) return add(acc, diffHeight(item.patch, m, extraPx));
|
|
9093
9581
|
const text = item.result?.text ?? "";
|
|
9094
9582
|
if (!text) return acc;
|
|
9095
|
-
const { shown, more } = collapsedResult(text.trimEnd().split("\n"));
|
|
9583
|
+
const { shown, more } = collapsedResult(text.trimEnd().split("\n"), item.result?.totalChars);
|
|
9096
9584
|
for (const line of shown) acc = add(acc, rowH(line || " ", m, {
|
|
9097
9585
|
indentCells: 3,
|
|
9098
9586
|
gutterCells: 3,
|
|
@@ -9138,9 +9626,14 @@ function itemHeight(item, m) {
|
|
|
9138
9626
|
};
|
|
9139
9627
|
}
|
|
9140
9628
|
}
|
|
9141
|
-
/** A virtual row's height: an item,
|
|
9142
|
-
* summary line, built by the same function
|
|
9629
|
+
/** A virtual row's height: an item, a folded tool run, or a task block —
|
|
9630
|
+
* either fold collapsed is its one summary line, built by the same function
|
|
9631
|
+
* the row draws (`runSummary` / `taskSummary`), rendered as one standard
|
|
9632
|
+
* 2-cell-gutter `Row`. No expanded branch for the task block either: it is
|
|
9633
|
+
* always collapsed by default, which is the invariant that keeps every
|
|
9634
|
+
* unmounted row's estimate exact. */
|
|
9143
9635
|
function blockHeight(block, m) {
|
|
9636
|
+
if ("task" in block) return rowH(taskSummary(block.task, taskChildItems(block)), m);
|
|
9144
9637
|
if ("run" in block) {
|
|
9145
9638
|
const busy = block.run.some((item) => item.status === "running" || item.status === "pending");
|
|
9146
9639
|
return rowH(runSummary(block.run, busy), m);
|
|
@@ -9157,22 +9650,27 @@ function nearestMember(cluster, y) {
|
|
|
9157
9650
|
return best?.mark;
|
|
9158
9651
|
}
|
|
9159
9652
|
/**
|
|
9160
|
-
*
|
|
9161
|
-
*
|
|
9162
|
-
*
|
|
9163
|
-
* a
|
|
9164
|
-
*
|
|
9165
|
-
*
|
|
9653
|
+
* The two lanes are **channels, not classes**: left is what went *in* — your
|
|
9654
|
+
* prompts, and the sub-agents you dispatched — and right is what came *out* —
|
|
9655
|
+
* each turn's answer, and everything that went wrong producing one. That is the
|
|
9656
|
+
* question a reader actually asks of a rail ("where did I say something", "where
|
|
9657
|
+
* did it go wrong"), and it puts every failure in one column instead of
|
|
9658
|
+
* scattering some down the middle.
|
|
9659
|
+
*
|
|
9660
|
+
* Full width is reserved for what is not a channel at all: a waiting approval
|
|
9661
|
+
* (which is the session asking *you*, pinned at the foot), a bookmark (the
|
|
9662
|
+
* reader's own annotation) and the catch-up seam (a boundary across both).
|
|
9166
9663
|
*
|
|
9167
9664
|
* It also buys the marks their width back: three lanes in a 16px rail is 5px a
|
|
9168
9665
|
* lane, which is a hard target to hit and a hard colour to see.
|
|
9169
9666
|
*/
|
|
9170
9667
|
const LANE = {
|
|
9171
9668
|
user: "l",
|
|
9669
|
+
subagent: "l",
|
|
9172
9670
|
turn: "r",
|
|
9173
9671
|
turnFailed: "r",
|
|
9174
|
-
toolFailed: "
|
|
9175
|
-
error: "
|
|
9672
|
+
toolFailed: "r",
|
|
9673
|
+
error: "r",
|
|
9176
9674
|
approval: "f",
|
|
9177
9675
|
recap: "f",
|
|
9178
9676
|
bookmark: "f"
|
|
@@ -9186,10 +9684,12 @@ const LOUDNESS = {
|
|
|
9186
9684
|
user: 3,
|
|
9187
9685
|
turn: 2,
|
|
9188
9686
|
bookmark: 1,
|
|
9687
|
+
subagent: 1,
|
|
9189
9688
|
recap: 0
|
|
9190
9689
|
};
|
|
9191
9690
|
const KIND_NAME = {
|
|
9192
9691
|
user: "you",
|
|
9692
|
+
subagent: "sub-agent",
|
|
9193
9693
|
turn: "response · turn end",
|
|
9194
9694
|
turnFailed: "turn failed",
|
|
9195
9695
|
toolFailed: "tool failed",
|
|
@@ -9235,9 +9735,27 @@ function excerpt(item) {
|
|
|
9235
9735
|
default: return "";
|
|
9236
9736
|
}
|
|
9237
9737
|
}
|
|
9738
|
+
/**
|
|
9739
|
+
* Exported for `test/scrubber.test.ts` and nothing else — it is not part of the
|
|
9740
|
+
* package's surface (`index.ts` does not re-export it). Both of the bugs this
|
|
9741
|
+
* function has shipped were pure-logic ones a unit test catches: a live answer
|
|
9742
|
+
* with no `turn_result` yet went unmarked for the whole two minutes it was the
|
|
9743
|
+
* only thing worth navigating to, and a replayed history — which carries no turn
|
|
9744
|
+
* rows at all — came back with an empty right lane.
|
|
9745
|
+
*/
|
|
9238
9746
|
function buildClusters(props, railH) {
|
|
9239
|
-
const { items, bookmarks, recapRow, pendingApprovals, rowIndexFor, offsetOfRow, sizeOfRow, totalSize, viewportH } = props;
|
|
9747
|
+
const { items, bookmarks, recapRow, pendingApprovals, rowIndexFor, offsetOfRow, sizeOfRow, positionInRow, totalSize, viewportH } = props;
|
|
9240
9748
|
const marks = [];
|
|
9749
|
+
const subagentParents = /* @__PURE__ */ new Set();
|
|
9750
|
+
for (const item of items) {
|
|
9751
|
+
const parent = parentOf(item);
|
|
9752
|
+
if (parent !== void 0) subagentParents.add(parent);
|
|
9753
|
+
}
|
|
9754
|
+
const rowOutcome = /* @__PURE__ */ new Map();
|
|
9755
|
+
items.forEach((item, index) => {
|
|
9756
|
+
if (item.kind !== "tool_call" || parentOf(item) !== void 0) return;
|
|
9757
|
+
rowOutcome.set(rowIndexFor(index), index);
|
|
9758
|
+
});
|
|
9241
9759
|
let segment = {};
|
|
9242
9760
|
const closeSegment = () => {
|
|
9243
9761
|
const anchor = segment.response ?? segment.turn;
|
|
@@ -9250,7 +9768,12 @@ function buildClusters(props, railH) {
|
|
|
9250
9768
|
segment = {};
|
|
9251
9769
|
};
|
|
9252
9770
|
items.forEach((item, index) => {
|
|
9253
|
-
if (item.kind === "
|
|
9771
|
+
if (item.kind === "tool_call" && subagentParents.has(item.id)) marks.push({
|
|
9772
|
+
kind: "subagent",
|
|
9773
|
+
itemIndex: index,
|
|
9774
|
+
rowIndex: rowIndexFor(index)
|
|
9775
|
+
});
|
|
9776
|
+
if (item.kind === "user" && parentOf(item) === void 0) {
|
|
9254
9777
|
closeSegment();
|
|
9255
9778
|
marks.push({
|
|
9256
9779
|
kind: "user",
|
|
@@ -9266,7 +9789,7 @@ function buildClusters(props, railH) {
|
|
|
9266
9789
|
itemIndex: index,
|
|
9267
9790
|
rowIndex: rowIndexFor(index)
|
|
9268
9791
|
});
|
|
9269
|
-
else if (item.kind === "tool_call" && (item.status === "failed" || item.result?.isError === true)) marks.push({
|
|
9792
|
+
else if (item.kind === "tool_call" && (item.status === "failed" || item.result?.isError === true) && rowOutcome.get(rowIndexFor(index)) === index) marks.push({
|
|
9270
9793
|
kind: "toolFailed",
|
|
9271
9794
|
itemIndex: index,
|
|
9272
9795
|
rowIndex: rowIndexFor(index)
|
|
@@ -9287,8 +9810,10 @@ function buildClusters(props, railH) {
|
|
|
9287
9810
|
const scale = railScale(railH, totalSize, viewportH);
|
|
9288
9811
|
const lanes = /* @__PURE__ */ new Map();
|
|
9289
9812
|
for (const mark of marks) {
|
|
9290
|
-
const
|
|
9291
|
-
const
|
|
9813
|
+
const within = mark.itemIndex >= 0 ? positionInRow?.(mark.itemIndex) : void 0;
|
|
9814
|
+
const rowH = sizeOfRow(mark.rowIndex);
|
|
9815
|
+
const h = within ? MIN_MARK : Math.max(MIN_MARK, Math.round(rowH * scale));
|
|
9816
|
+
const y = Math.min(Math.max(0, railH - h), Math.round((offsetOfRow(mark.rowIndex) + (within ? within.ordinal / within.count * rowH : 0)) * scale));
|
|
9292
9817
|
const lane = LANE[mark.kind];
|
|
9293
9818
|
const list = lanes.get(lane) ?? [];
|
|
9294
9819
|
list.push({
|
|
@@ -9560,12 +10085,14 @@ function TerminalScrubber(props) {
|
|
|
9560
10085
|
}
|
|
9561
10086
|
//#endregion
|
|
9562
10087
|
//#region src/components/agent/transcript-rows.ts
|
|
9563
|
-
/** The item a row is spaced *as*. A run stands for the calls it folded,
|
|
9564
|
-
*
|
|
10088
|
+
/** The item a row is spaced *as*. A run stands for the calls it folded, and a
|
|
10089
|
+
* task block for the `Task` call it absorbed into — all tool calls, so a run,
|
|
10090
|
+
* a task and a lone tool call below them still read as one block. */
|
|
9565
10091
|
function rowItem(row) {
|
|
9566
10092
|
if (!row) return void 0;
|
|
9567
10093
|
if ("item" in row) return row.item;
|
|
9568
10094
|
if ("run" in row) return row.run[0];
|
|
10095
|
+
if ("task" in row) return row.task;
|
|
9569
10096
|
}
|
|
9570
10097
|
/**
|
|
9571
10098
|
* Does a blank line go above this row, in the terminal theme?
|
|
@@ -9581,26 +10108,63 @@ function gapBefore(rows, index) {
|
|
|
9581
10108
|
return needsBlank(before, after);
|
|
9582
10109
|
}
|
|
9583
10110
|
/**
|
|
10111
|
+
* Which items each task block absorbed, as itemIndex → rowIndex — the one
|
|
10112
|
+
* lookup {@link rowIndexForItem} cannot answer from ordering (see its comment).
|
|
10113
|
+
* Memoized per rows array identity: the shell builds `rows` in a `useMemo`, so
|
|
10114
|
+
* within one row list this is built once, and a WeakMap means a discarded list
|
|
10115
|
+
* takes its map with it. Memoization only — the answer is a pure function of
|
|
10116
|
+
* the array.
|
|
10117
|
+
*/
|
|
10118
|
+
const absorbedCache = /* @__PURE__ */ new WeakMap();
|
|
10119
|
+
function absorbedRows(rows) {
|
|
10120
|
+
const hit = absorbedCache.get(rows);
|
|
10121
|
+
if (hit) return hit;
|
|
10122
|
+
const map = /* @__PURE__ */ new Map();
|
|
10123
|
+
rows.forEach((row, rowIndex) => {
|
|
10124
|
+
if ("task" in row) for (const itemIndex of row.childIndices) map.set(itemIndex, rowIndex);
|
|
10125
|
+
});
|
|
10126
|
+
absorbedCache.set(rows, map);
|
|
10127
|
+
return map;
|
|
10128
|
+
}
|
|
10129
|
+
/**
|
|
9584
10130
|
* Transcript-item index → virtual-row index — **the off-by-a-fold trap.**
|
|
9585
10131
|
*
|
|
9586
10132
|
* 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
|
-
*
|
|
10133
|
+
* run occupies ONE row for `run.length` consecutive items, a task block
|
|
10134
|
+
* occupies ONE row for its `Task` call *plus every item its subagent produced*,
|
|
10135
|
+
* and the recap boundary is a row with *no* item index at all, shifting every
|
|
10136
|
+
* row after it by one. `virtualizer.scrollToIndex(itemIndex)` is therefore
|
|
10137
|
+
* wrong by construction on any folded or spliced transcript — every jump that
|
|
10138
|
+
* starts from an item (the scrubber's marks, a future bookmark) must come
|
|
10139
|
+
* through here first.
|
|
10140
|
+
*
|
|
10141
|
+
* The contract, in two halves:
|
|
10142
|
+
*
|
|
10143
|
+
* - An index a task block **absorbed** maps to that block's row, wherever the
|
|
10144
|
+
* child fell in the stream. Subagents run in parallel, so absorbed indices
|
|
10145
|
+
* interleave arbitrarily with later rows' starts — no ordering argument can
|
|
10146
|
+
* find their row, which is why they are answered first, from a per-row-list
|
|
10147
|
+
* map ({@link absorbedRows}) built once per rows array. A row's coverage is
|
|
10148
|
+
* its `childIndices`, never `[index, index + N)` arithmetic.
|
|
10149
|
+
* - Every other index maps to the **last non-recap row whose start (`index`)
|
|
10150
|
+
* is ≤ the target** — the original rule, still a binary search. Rows stay
|
|
10151
|
+
* ordered by `index`, and the ordering argument is now: between one row's
|
|
10152
|
+
* start and the next row's, every index is either absorbed (answered above)
|
|
10153
|
+
* or a member of the earlier row — note that is *weaker* than the old
|
|
10154
|
+
* contiguity claim, because a run can fold across an absorbed gap (two
|
|
10155
|
+
* top-level calls separated only by a subagent's step are adjacent on
|
|
10156
|
+
* screen), so `[index, index + run.length)` arithmetic no longer describes
|
|
10157
|
+
* a run's coverage; membership does. The recap row is skipped by giving it
|
|
10158
|
+
* its successor's start for navigation (both qualify at the boundary, and
|
|
10159
|
+
* "last wins" lands on the real row) while never letting it be the answer.
|
|
10160
|
+
*
|
|
10161
|
+
* Exhaustively checked against a linear reference — every fixture × every item
|
|
10162
|
+
* index × several splice positions — by `__wdCheckMapping` in `dev/App.tsx`,
|
|
10163
|
+
* and against constructed interleavings in `test/transcript-rows.test.ts`.
|
|
9602
10164
|
*/
|
|
9603
10165
|
function rowIndexForItem(rows, itemIndex) {
|
|
10166
|
+
const absorbed = absorbedRows(rows).get(itemIndex);
|
|
10167
|
+
if (absorbed !== void 0) return absorbed;
|
|
9604
10168
|
let lo = 0;
|
|
9605
10169
|
let hi = rows.length - 1;
|
|
9606
10170
|
let best = 0;
|
|
@@ -9620,6 +10184,49 @@ function rowIndexForItem(rows, itemIndex) {
|
|
|
9620
10184
|
}
|
|
9621
10185
|
return best;
|
|
9622
10186
|
}
|
|
10187
|
+
const positionCache = /* @__PURE__ */ new WeakMap();
|
|
10188
|
+
function rowPositions(rows) {
|
|
10189
|
+
const hit = positionCache.get(rows);
|
|
10190
|
+
if (hit) return hit;
|
|
10191
|
+
const map = /* @__PURE__ */ new Map();
|
|
10192
|
+
for (const row of rows) if ("task" in row) {
|
|
10193
|
+
const count = row.childIndices.length;
|
|
10194
|
+
row.childIndices.forEach((itemIndex, ordinal) => map.set(itemIndex, {
|
|
10195
|
+
ordinal,
|
|
10196
|
+
count
|
|
10197
|
+
}));
|
|
10198
|
+
} else if ("run" in row && row.run.length > 1) {
|
|
10199
|
+
const count = row.run.length;
|
|
10200
|
+
row.indices.forEach((itemIndex, ordinal) => map.set(itemIndex, {
|
|
10201
|
+
ordinal,
|
|
10202
|
+
count
|
|
10203
|
+
}));
|
|
10204
|
+
}
|
|
10205
|
+
positionCache.set(rows, map);
|
|
10206
|
+
return map;
|
|
10207
|
+
}
|
|
10208
|
+
/**
|
|
10209
|
+
* Where an item sits inside a row that holds MORE than itself — a task block's
|
|
10210
|
+
* absorbed child, or a member of a folded run of two or more. `undefined` for
|
|
10211
|
+
* everything else, including a row's own head item (the `Task` call, a run's
|
|
10212
|
+
* first member is *not* exempt) and a **singleton run**: there the row's extent
|
|
10213
|
+
* IS the item's, and a mark spanning it is honest.
|
|
10214
|
+
*
|
|
10215
|
+
* That carve-out is load-bearing rather than tidy: `pushLeaf` makes *every*
|
|
10216
|
+
* top-level tool call a `RunBlock`, usually of length 1, so without it every
|
|
10217
|
+
* ordinary failed call's scrubber mark would shrink from its row's extent to a
|
|
10218
|
+
* tick and the rail would stop reading as a map — a regression traded for a fix.
|
|
10219
|
+
*
|
|
10220
|
+
* The scrubber is the consumer: a mark for a shared-row item anchors at
|
|
10221
|
+
* `ordinal / count` of the row's *measured* height instead of inheriting an
|
|
10222
|
+
* extent that is mostly other items' work (one failed child of a hundred-call
|
|
10223
|
+
* task painted the whole expanded block red). Memoized per rows array identity
|
|
10224
|
+
* exactly like {@link absorbedRows}, and pure — the answer is a function of the
|
|
10225
|
+
* array alone, and a discarded array takes its map with it.
|
|
10226
|
+
*/
|
|
10227
|
+
function positionInRow(rows, itemIndex) {
|
|
10228
|
+
return rowPositions(rows).get(itemIndex);
|
|
10229
|
+
}
|
|
9623
10230
|
//#endregion
|
|
9624
10231
|
//#region src/components/agent/use-height-epoch.ts
|
|
9625
10232
|
/**
|
|
@@ -9788,6 +10395,55 @@ function TerminalItemView({ item, fileUrl }) {
|
|
|
9788
10395
|
default: return null;
|
|
9789
10396
|
}
|
|
9790
10397
|
}
|
|
10398
|
+
/**
|
|
10399
|
+
* A `Task` and everything the subagent it spawned produced, as one row.
|
|
10400
|
+
*
|
|
10401
|
+
* The same claim the tool-run fold makes, and a stronger one: a subagent is
|
|
10402
|
+
* *sixty* rows of somebody else's working — a brief, a dozen greps, its own
|
|
10403
|
+
* thinking — and none of it is what you came back to read. What you came back
|
|
10404
|
+
* to read is the report, and the report is the model's next sentence. So the
|
|
10405
|
+
* whole frame collapses to one line saying what was asked and how big the
|
|
10406
|
+
* answer was, and opens in full the moment it is the thing you want.
|
|
10407
|
+
*
|
|
10408
|
+
* **Always collapsed when unmounted**, and that is load-bearing rather than
|
|
10409
|
+
* tidy: `height.ts` predicts this row as exactly one wrapped `taskSummary`, and
|
|
10410
|
+
* expansion is component-local state that dies with the row. A row auto-opening
|
|
10411
|
+
* because its subagent happens to be running would make its own height
|
|
10412
|
+
* unpredictable — which is why the live signal is *in* the collapsed line (the
|
|
10413
|
+
* pulse, and a count that climbs) rather than in an open block.
|
|
10414
|
+
*
|
|
10415
|
+
* The children are the theme's ordinary rows, stepped in behind a rule, and
|
|
10416
|
+
* they fold among themselves: a subagent's consecutive tool calls are as much
|
|
10417
|
+
* an aside inside its frame as they are in the main thread.
|
|
10418
|
+
*/
|
|
10419
|
+
function TaskRow({ block, fileUrl }) {
|
|
10420
|
+
const [open, setOpen] = useState(false);
|
|
10421
|
+
const reveal = useRevealOnOpen(open);
|
|
10422
|
+
const children = useMemo(() => taskChildItems(block), [block]);
|
|
10423
|
+
const busy = taskBusy(block.task, children);
|
|
10424
|
+
const failed = taskFailed(block.task);
|
|
10425
|
+
const pulse = usePulse(busy);
|
|
10426
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10427
|
+
ref: reveal,
|
|
10428
|
+
className: open ? "term-open" : void 0,
|
|
10429
|
+
children: [/* @__PURE__ */ jsx(Pressable, {
|
|
10430
|
+
onPress: () => setOpen((v) => !v),
|
|
10431
|
+
expanded: open,
|
|
10432
|
+
children: /* @__PURE__ */ jsx(Row, {
|
|
10433
|
+
glyph: busy ? pulse : "●",
|
|
10434
|
+
glyphTone: failed ? "red" : busy ? "mark" : "dim",
|
|
10435
|
+
tone: failed ? "red" : "fg",
|
|
10436
|
+
children: taskSummary(block.task, children)
|
|
10437
|
+
})
|
|
10438
|
+
}), open ? /* @__PURE__ */ jsx("div", {
|
|
10439
|
+
className: "term-nested",
|
|
10440
|
+
children: 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, {
|
|
10441
|
+
item: leaf.item,
|
|
10442
|
+
fileUrl
|
|
10443
|
+
})] }, leaf.key))
|
|
10444
|
+
}) : null]
|
|
10445
|
+
});
|
|
10446
|
+
}
|
|
9791
10447
|
/** When the current run began — the clock the working line counts from. Held
|
|
9792
10448
|
* here, not in the row, because the row comes and goes within a single turn (it
|
|
9793
10449
|
* hides the moment text streams) and a clock restarting at every tool call
|
|
@@ -9819,9 +10475,12 @@ function TerminalTranscript({ state, fileUrl, fontSize, lineHeight, affordances,
|
|
|
9819
10475
|
affordances,
|
|
9820
10476
|
bleed: "1ch",
|
|
9821
10477
|
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, {
|
|
10478
|
+
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
10479
|
item: block.item,
|
|
9824
10480
|
fileUrl
|
|
10481
|
+
}) : /* @__PURE__ */ jsx(TaskRow, {
|
|
10482
|
+
block,
|
|
10483
|
+
fileUrl
|
|
9825
10484
|
})] }, block.key)), working(state) ? /* @__PURE__ */ jsxs(Fragment$1, { children: [state.items.length > 0 ? /* @__PURE__ */ jsx(Blank, {}) : null, /* @__PURE__ */ jsx(WorkingRow, {
|
|
9826
10485
|
label: state.status === "starting" ? "Starting…" : "Working…",
|
|
9827
10486
|
startedAt: runStartedAt,
|
|
@@ -10133,10 +10792,10 @@ function StickyPromptLane({ top, height, gapClass, scrollRoot, index, measureRef
|
|
|
10133
10792
|
* transient UI state (an expanded tool card, an opened reasoning block) resets
|
|
10134
10793
|
* once the row scrolls far enough away to unmount.
|
|
10135
10794
|
*/
|
|
10136
|
-
function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyPrompt, gap, fontSize, lineHeight, items, pendingApprovals, scrubber, scrubberMarks, affordances, fileUrl, attachmentUrl, hostImage, jumpToRecapRef, repinRef }) {
|
|
10795
|
+
function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyPrompt, gap, fontSize, lineHeight, items, pendingApprovals, scrubber, scrubberMarks, affordances, fileUrl, attachmentUrl, hostImage, jumpToRecapRef, repinRef, reveal }) {
|
|
10137
10796
|
const stick = useStickToBottomContext();
|
|
10138
10797
|
const [scrollElement, setScrollElement] = useState(null);
|
|
10139
|
-
const promptRows = useMemo(() => rows.flatMap((row, index) => "item" in row && row.item.kind === "user" ? [index] : []), [rows]);
|
|
10798
|
+
const promptRows = useMemo(() => rows.flatMap((row, index) => "item" in row && row.item.kind === "user" && parentOf(row.item) === void 0 ? [index] : []), [rows]);
|
|
10140
10799
|
const pinRef = useRef({
|
|
10141
10800
|
enabled: false,
|
|
10142
10801
|
promptRows: []
|
|
@@ -10185,7 +10844,7 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
10185
10844
|
if (terminal && epoch) {
|
|
10186
10845
|
const row = rows[index];
|
|
10187
10846
|
const gapPx = index > 0 && gapBefore(rows, index) ? epoch.line : 0;
|
|
10188
|
-
if (row && ("
|
|
10847
|
+
if (row && !("line" in row)) return estimateBlockPx(row, epoch) + gapPx;
|
|
10189
10848
|
return epoch.line + gapPx;
|
|
10190
10849
|
}
|
|
10191
10850
|
return (terminal ? 36 : 100) + gap.px;
|
|
@@ -10239,6 +10898,14 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
10239
10898
|
jumpToRecapRef,
|
|
10240
10899
|
repinRef
|
|
10241
10900
|
});
|
|
10901
|
+
const revealNonce = reveal?.nonce;
|
|
10902
|
+
const revealId = reveal?.toolUseId;
|
|
10903
|
+
useEffect(() => {
|
|
10904
|
+
if (revealId === void 0) return;
|
|
10905
|
+
const itemIndex = items.findIndex((item) => item.kind === "tool_call" && item.id === revealId);
|
|
10906
|
+
if (itemIndex < 0) return;
|
|
10907
|
+
jumpToRow(rowIndexForItem(rows, itemIndex), "start");
|
|
10908
|
+
}, [revealNonce]);
|
|
10242
10909
|
const scrubInteractive = resolveAffordances(affordances).hover;
|
|
10243
10910
|
const recapIndex = rows.findIndex((row) => row.key === "recap");
|
|
10244
10911
|
const recapRow = recapIndex >= 0 ? {
|
|
@@ -10314,12 +10981,18 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
10314
10981
|
hostImage,
|
|
10315
10982
|
terminal
|
|
10316
10983
|
})
|
|
10317
|
-
}) : /* @__PURE__ */ jsx(RecapRow, {
|
|
10984
|
+
}) : "line" in row ? /* @__PURE__ */ jsx(RecapRow, {
|
|
10318
10985
|
line: row.line,
|
|
10319
10986
|
since,
|
|
10320
10987
|
terminal
|
|
10988
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
10989
|
+
className: cn(read(boundary, row.index) && "opacity-45"),
|
|
10990
|
+
children: /* @__PURE__ */ jsx(TaskRow, {
|
|
10991
|
+
block: row,
|
|
10992
|
+
fileUrl
|
|
10993
|
+
})
|
|
10321
10994
|
});
|
|
10322
|
-
if (terminal && stickyPrompt && "item" in row && row.item.kind === "user") {
|
|
10995
|
+
if (terminal && stickyPrompt && "item" in row && row.item.kind === "user" && parentOf(row.item) === void 0) {
|
|
10323
10996
|
const next = promptRows.find((index) => index > virtualRow.index);
|
|
10324
10997
|
const laneEnd = next === void 0 ? virtualizer.getTotalSize() : measurements[next]?.start ?? virtualRow.start;
|
|
10325
10998
|
return /* @__PURE__ */ jsx(StickyPromptLane, {
|
|
@@ -10345,6 +11018,7 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
10345
11018
|
recapRow,
|
|
10346
11019
|
bookmarks: scrubberMarks ?? [],
|
|
10347
11020
|
rowIndexFor: (itemIndex) => rowIndexForItem(rows, itemIndex),
|
|
11021
|
+
positionInRow: (itemIndex) => positionInRow(rows, itemIndex),
|
|
10348
11022
|
offsetOfRow: (rowIndex) => virtualizer.measurementsCache[rowIndex]?.start ?? 0,
|
|
10349
11023
|
sizeOfRow: (rowIndex) => virtualizer.measurementsCache[rowIndex]?.size ?? 0,
|
|
10350
11024
|
totalSize: virtualizer.getTotalSize(),
|
|
@@ -10357,7 +11031,7 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
|
|
|
10357
11031
|
}), scrollElement.parentElement) : null]
|
|
10358
11032
|
});
|
|
10359
11033
|
}
|
|
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 }) {
|
|
11034
|
+
function Transcript({ state, fileUrl, attachmentUrl, canBrowseFiles, hostImage, variant = "cards", density = "comfortable", fontSize, lineHeight, affordances, stickyPrompt = false, scrubber, scrubberMarks, replaying = false, catchUp, jumpToRecapRef, repinRef, reveal, className }) {
|
|
10361
11035
|
const terminal = variant === "terminal";
|
|
10362
11036
|
const gap = ROW_GAP[variant][density];
|
|
10363
11037
|
const runStartedAt = useRunStart(state.status);
|
|
@@ -10416,7 +11090,8 @@ function Transcript({ state, fileUrl, attachmentUrl, canBrowseFiles, hostImage,
|
|
|
10416
11090
|
attachmentUrl,
|
|
10417
11091
|
hostImage,
|
|
10418
11092
|
jumpToRecapRef,
|
|
10419
|
-
repinRef
|
|
11093
|
+
repinRef,
|
|
11094
|
+
reveal
|
|
10420
11095
|
}), showLoader(state) ? terminal ? /* @__PURE__ */ jsxs(Fragment$1, { children: [state.items.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
10421
11096
|
className: "term-blank",
|
|
10422
11097
|
"aria-hidden": true
|
|
@@ -10650,14 +11325,14 @@ const INTERACTIVE = [
|
|
|
10650
11325
|
* the engine name — an absent capability hides the control instead of offering
|
|
10651
11326
|
* one that can only fail.
|
|
10652
11327
|
*/
|
|
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 }) {
|
|
11328
|
+
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, stickyPrompt = false, controlsSurface = "internal", onControls, focusComposerOnClick = false, unseen, readOnly = false, toolHost, cacheTranscript, className }) {
|
|
10654
11329
|
const external = panelSurface === "external";
|
|
10655
11330
|
const statusExternal = statusSurface === "external";
|
|
10656
11331
|
const controlsInStatus = controlsSurface === "status" && !statusExternal;
|
|
10657
11332
|
const controlsExternal = controlsSurface === "external" || controlsInStatus;
|
|
10658
11333
|
const [protocolError, setProtocolError] = useState(void 0);
|
|
10659
11334
|
const [panel, setPanel] = useState();
|
|
10660
|
-
const { state, connection, replaying, protocolMismatch, models, effectiveModel, handle, send, approve, deny, interrupt, setModel, setPermissionMode, reconnectNow } = useClaudeSession(client, sessionId, {
|
|
11335
|
+
const { state, connection, replaying, protocolMismatch, models, effectiveModel, handle, send, approve, deny, interrupt, setModel, setPermissionMode, reconnectNow, loadFullResult } = useClaudeSession(client, sessionId, {
|
|
10661
11336
|
onProtocolError: setProtocolError,
|
|
10662
11337
|
cacheTranscript
|
|
10663
11338
|
});
|
|
@@ -10762,6 +11437,7 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
|
|
|
10762
11437
|
const hostFiles = useHostFileSearch(client, state.cwd);
|
|
10763
11438
|
const windows = useMemo(() => orderUsageWindows(usage), [usage]);
|
|
10764
11439
|
const hostImage = useHostImage(client, sessionId, state.producedFiles);
|
|
11440
|
+
const resultImages = useToolResultImages(client, sessionId);
|
|
10765
11441
|
const composerRef = useRef(null);
|
|
10766
11442
|
const jumpToRecap = useRef(null);
|
|
10767
11443
|
const repinTranscript = useRef(null);
|
|
@@ -10858,178 +11534,185 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
|
|
|
10858
11534
|
value: transcriptVariant,
|
|
10859
11535
|
children: /* @__PURE__ */ jsx(TranscriptDensityProvider, {
|
|
10860
11536
|
value: transcriptDensity,
|
|
10861
|
-
children: /* @__PURE__ */
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
level: "warning",
|
|
11537
|
+
children: /* @__PURE__ */ jsx(ToolResultFetchProvider, {
|
|
11538
|
+
value: loadFullResult,
|
|
11539
|
+
children: /* @__PURE__ */ jsx(ToolResultImageProvider, {
|
|
11540
|
+
value: resultImages,
|
|
11541
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
11542
|
+
"data-slot": "session-panel",
|
|
11543
|
+
"data-agent-font": transcriptFont,
|
|
11544
|
+
onClick: handleClick,
|
|
11545
|
+
className: cn("flex h-full min-h-0 flex-col overflow-hidden bg-bg", className),
|
|
10871
11546
|
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
|
-
/* @__PURE__ */ jsxs("span", {
|
|
10918
|
-
className: "min-w-0 flex-1 truncate",
|
|
11547
|
+
headerTakesActions ? header({ actions: menu }) : header,
|
|
11548
|
+
statusPlacement === "top" ? statusBar : null,
|
|
11549
|
+
protocolMismatch !== void 0 ? /* @__PURE__ */ jsxs(Notice, {
|
|
11550
|
+
level: "warning",
|
|
11551
|
+
children: [
|
|
11552
|
+
"Server speaks protocol v",
|
|
11553
|
+
protocolMismatch,
|
|
11554
|
+
", this build renders v",
|
|
11555
|
+
PROTOCOL_VERSION,
|
|
11556
|
+
". Some events may not render."
|
|
11557
|
+
]
|
|
11558
|
+
}) : null,
|
|
11559
|
+
protocolError ? /* @__PURE__ */ jsx(Notice, {
|
|
11560
|
+
level: "error",
|
|
11561
|
+
onDismiss: () => setProtocolError(void 0),
|
|
11562
|
+
children: protocolError
|
|
11563
|
+
}) : null,
|
|
11564
|
+
/* @__PURE__ */ jsx(Transcript, {
|
|
11565
|
+
state,
|
|
11566
|
+
fileUrl: sessionId ? (path) => client.sessionFileUrl(sessionId, path) : void 0,
|
|
11567
|
+
attachmentUrl: sessionId ? (id) => client.attachmentUrl(sessionId, id) : void 0,
|
|
11568
|
+
canBrowseFiles: hostFiles.available,
|
|
11569
|
+
hostImage,
|
|
11570
|
+
variant: transcriptVariant,
|
|
11571
|
+
density: transcriptDensity,
|
|
11572
|
+
fontSize: terminalMetrics?.fontSize,
|
|
11573
|
+
lineHeight: terminalMetrics?.lineHeight,
|
|
11574
|
+
affordances,
|
|
11575
|
+
stickyPrompt,
|
|
11576
|
+
scrubber,
|
|
11577
|
+
scrubberMarks,
|
|
11578
|
+
replaying,
|
|
11579
|
+
catchUp: catchUp && newCount > 0 ? {
|
|
11580
|
+
from: catchUp.itemCount,
|
|
11581
|
+
since: catchUp.since
|
|
11582
|
+
} : void 0,
|
|
11583
|
+
reveal,
|
|
11584
|
+
jumpToRecapRef: jumpToRecap,
|
|
11585
|
+
repinRef: repinTranscript
|
|
11586
|
+
}),
|
|
11587
|
+
catchUp && newCount > 0 && !replaying ? /* @__PURE__ */ jsx("div", {
|
|
11588
|
+
className: "px-3 pb-1",
|
|
11589
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
11590
|
+
"data-slot": "catch-up",
|
|
11591
|
+
className: "mx-auto flex w-full max-w-[var(--wd-content-max-w,48rem)] items-center gap-2 text-label text-fg-3",
|
|
10919
11592
|
children: [
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
|
|
11593
|
+
/* @__PURE__ */ jsx("span", {
|
|
11594
|
+
"aria-hidden": true,
|
|
11595
|
+
className: cn("select-none", terminal ? "text-fg-3" : "text-accent"),
|
|
11596
|
+
children: "※"
|
|
11597
|
+
}),
|
|
11598
|
+
/* @__PURE__ */ jsxs("span", {
|
|
11599
|
+
className: "min-w-0 flex-1 truncate",
|
|
11600
|
+
children: [
|
|
11601
|
+
newCount,
|
|
11602
|
+
" new ",
|
|
11603
|
+
newCount === 1 ? "row" : "rows",
|
|
11604
|
+
catchUp.since !== void 0 ? ` since you were last here` : ""
|
|
11605
|
+
]
|
|
11606
|
+
}),
|
|
11607
|
+
/* @__PURE__ */ jsx("button", {
|
|
11608
|
+
type: "button",
|
|
11609
|
+
onClick: () => jumpToRecap.current?.(),
|
|
11610
|
+
className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
|
|
11611
|
+
children: "jump"
|
|
11612
|
+
}),
|
|
11613
|
+
/* @__PURE__ */ jsx("button", {
|
|
11614
|
+
type: "button",
|
|
11615
|
+
onClick: () => setCaughtUp(true),
|
|
11616
|
+
className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
|
|
11617
|
+
children: "dismiss"
|
|
11618
|
+
})
|
|
10924
11619
|
]
|
|
11620
|
+
})
|
|
11621
|
+
}) : null,
|
|
11622
|
+
!readOnly && capabilities.interactiveApprovals && state.pendingApprovals.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
11623
|
+
className: cn(terminal ? "pb-2" : "px-3 pb-2"),
|
|
11624
|
+
children: /* @__PURE__ */ jsx(PromptSurface, {
|
|
11625
|
+
terminal,
|
|
11626
|
+
metrics: terminalMetrics,
|
|
11627
|
+
affordances,
|
|
11628
|
+
children: state.pendingApprovals.map((request) => {
|
|
11629
|
+
const isQuestion = request.toolName === "AskUserQuestion" && parseUserQuestions(request.input).length > 0;
|
|
11630
|
+
if (terminal) return isQuestion ? /* @__PURE__ */ jsx(TerminalQuestionPrompt, {
|
|
11631
|
+
request,
|
|
11632
|
+
onAnswer: approve,
|
|
11633
|
+
onDismiss: (id) => deny(id, "Question dismissed by user")
|
|
11634
|
+
}, request.id) : /* @__PURE__ */ jsx(TerminalPermissionPrompt, {
|
|
11635
|
+
request,
|
|
11636
|
+
onApprove: approve,
|
|
11637
|
+
onDeny: deny
|
|
11638
|
+
}, request.id);
|
|
11639
|
+
return isQuestion ? /* @__PURE__ */ jsx(QuestionPrompt, {
|
|
11640
|
+
request,
|
|
11641
|
+
onAnswer: approve,
|
|
11642
|
+
onDismiss: (id) => deny(id, "Question dismissed by user")
|
|
11643
|
+
}, request.id) : /* @__PURE__ */ jsx(PermissionPrompt, {
|
|
11644
|
+
request,
|
|
11645
|
+
onApprove: approve,
|
|
11646
|
+
onDeny: deny
|
|
11647
|
+
}, request.id);
|
|
11648
|
+
})
|
|
11649
|
+
})
|
|
11650
|
+
}) : null,
|
|
11651
|
+
readOnly ? null : /* @__PURE__ */ jsx(Composer, {
|
|
11652
|
+
ref: composerRef,
|
|
11653
|
+
onSend: handleSend,
|
|
11654
|
+
onInterrupt: interrupt,
|
|
11655
|
+
busy,
|
|
11656
|
+
disabled: ended || !sessionId,
|
|
11657
|
+
commands: capabilities.slashCommands ? commands : void 0,
|
|
11658
|
+
skills: capabilities.skillsList ? state.skills : void 0,
|
|
11659
|
+
attachments,
|
|
11660
|
+
onSearchFiles: hostFiles.available ? (query, options) => hostFiles.search(query, {
|
|
11661
|
+
...options,
|
|
11662
|
+
limit: 8
|
|
11663
|
+
}) : void 0,
|
|
11664
|
+
layout: controlsExternal ? "inline" : "stacked",
|
|
11665
|
+
toolbar: controlsExternal ? void 0 : sessionControls,
|
|
11666
|
+
fontSize: terminalMetrics?.fontSize,
|
|
11667
|
+
lineHeight: terminalMetrics?.lineHeight,
|
|
11668
|
+
affordances
|
|
11669
|
+
}),
|
|
11670
|
+
statusPlacement === "bottom" ? statusBar : null,
|
|
11671
|
+
!external ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
11672
|
+
/* @__PURE__ */ jsx(SessionInfoDialog, {
|
|
11673
|
+
state,
|
|
11674
|
+
client,
|
|
11675
|
+
sessionId,
|
|
11676
|
+
open: panel === "info",
|
|
11677
|
+
onOpenChange: (next) => setPanel(next ? "info" : void 0)
|
|
11678
|
+
}),
|
|
11679
|
+
/* @__PURE__ */ jsx(ContextDialog, {
|
|
11680
|
+
usage: state.contextUsage,
|
|
11681
|
+
open: panel === "context",
|
|
11682
|
+
onOpenChange: (next) => setPanel(next ? "context" : void 0)
|
|
11683
|
+
}),
|
|
11684
|
+
/* @__PURE__ */ jsx(UsageDialog, {
|
|
11685
|
+
rateLimits: windows,
|
|
11686
|
+
subscriptionType: state.subscriptionType,
|
|
11687
|
+
engine: state.engine ?? "claude",
|
|
11688
|
+
totalCostUsd: state.totalCostUsd,
|
|
11689
|
+
updatedAt: usageUpdatedAt,
|
|
11690
|
+
open: panel === "usage",
|
|
11691
|
+
onOpenChange: (next) => setPanel(next ? "usage" : void 0)
|
|
10925
11692
|
}),
|
|
10926
|
-
/* @__PURE__ */ jsx(
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
11693
|
+
/* @__PURE__ */ jsx(McpDialog, {
|
|
11694
|
+
client,
|
|
11695
|
+
sessionId,
|
|
11696
|
+
canManageServers: capabilities.mcpServerActions,
|
|
11697
|
+
open: panel === "mcp",
|
|
11698
|
+
onOpenChange: (next) => setPanel(next ? "mcp" : void 0)
|
|
10931
11699
|
}),
|
|
10932
|
-
/* @__PURE__ */ jsx(
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
|
|
11700
|
+
/* @__PURE__ */ jsx(SkillsDialog, {
|
|
11701
|
+
skills: state.skills,
|
|
11702
|
+
open: panel === "skills",
|
|
11703
|
+
onOpenChange: (next) => setPanel(next ? "skills" : void 0),
|
|
11704
|
+
onUse: (skill) => composerRef.current?.insertText(skillPrompt(skill))
|
|
11705
|
+
}),
|
|
11706
|
+
/* @__PURE__ */ jsx(HostFilesDialog, {
|
|
11707
|
+
client,
|
|
11708
|
+
cwd: state.cwd,
|
|
11709
|
+
open: panel === "files",
|
|
11710
|
+
onOpenChange: (next) => setPanel(next ? "files" : void 0)
|
|
10937
11711
|
})
|
|
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
|
-
]
|
|
11712
|
+
] }) : null
|
|
11713
|
+
]
|
|
11714
|
+
})
|
|
11715
|
+
})
|
|
11033
11716
|
})
|
|
11034
11717
|
})
|
|
11035
11718
|
});
|
|
@@ -11124,4 +11807,4 @@ function Notice({ level, onDismiss, children }) {
|
|
|
11124
11807
|
//#endregion
|
|
11125
11808
|
export { TranscriptDensityProvider as $, PermissionModeSelect as A, MenuTrigger as At, TerminalSurface as B, Button as Bt, TerminalQuestionPrompt as C, DialogHeader as Ct, parseUserQuestions as D, MenuContent as Dt, QuestionPrompt as E, Menu$1 as Et, McpDialog as F, SelectTrigger as Ft, Blank as G, TerminalDiff as H, cn as Ht, HostFilesDialog as I, SelectValue as It, CopyAction as J, Ink as K, ContextDialog as L, Input as Lt, permissionModeMeta as M, SelectContent as Mt, ModelSelect as N, SelectItem as Nt, PermissionPrompt as O, MenuItem as Ot, SkillsDialog as P, SelectItemText as Pt, toolIcon as Q, Composer as R, Badge as Rt, SessionInfoDialog as S, DialogContent as St, QUESTION_BEHAVIORS as T, DialogTrigger as Tt, previewPatch as U, TerminalMarkdown as V, buttonVariants as Vt, Band as W, useAffordances as X, WithActions as Y, isMutatingTool as Z, Conversation as _, TooltipContent as _t, Transcript as a, mentionTrigger as at, StatusBar as b, DialogBody as bt, ToolCallCard as c, plainTextToSegments as ct, Response as d, Splitter as dt, TranscriptVariantProvider as et, PromptTokenText as f, CodeBlock as ft, FileCard as g, Tip as gt, Loader as h, copyText as ht, useMinuteClock as i, hashtagTrigger as it, permissionModeChoices as j, Select$1 as jt, PERMISSION_MODES as k, MenuSeparator as kt, SessionEmptyState as l, segmentsToPlainText as lt, MessageContent as m, CopyButton as mt, UsageDialog as n, useTranscriptVariant as nt, TerminalItemView as o, usePromptAreaState as ot, Message as p, Spinner as pt, Row as q, UsageMeters as r, commandTrigger as rt, TerminalTranscript as s, PromptArea as st, SessionPanel as t, useTranscriptDensity as tt, Reasoning as u, ProgressRing as ut, ConversationContent as v, TooltipProvider as vt, TerminalPermissionPrompt as w, DialogRow as wt, STATUS_META as x, DialogClose as xt, ConversationScrollButton as y, Dialog$1 as yt, skillPrompt as z, badgeVariants as zt };
|
|
11126
11809
|
|
|
11127
|
-
//# sourceMappingURL=SessionPanel-
|
|
11810
|
+
//# sourceMappingURL=SessionPanel-DMPhsNlW.mjs.map
|