@opengeni/react 0.12.0 → 0.13.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 +1 -1
- package/dist/{chunk-5TIXRCSI.js → chunk-NFYVQWIB.js} +150 -64
- package/dist/chunk-NFYVQWIB.js.map +1 -0
- package/dist/index.d.ts +760 -142
- package/dist/index.js +5345 -2146
- package/dist/index.js.map +1 -1
- package/dist/{machines-BeZ3bD3t.d.ts → machines-CnlMb7E-.d.ts} +5 -5
- package/dist/machines.d.ts +1 -1
- package/dist/machines.js +1 -1
- package/package.json +17 -12
- package/src/approvals.ts +16 -4
- package/src/client.ts +32 -5
- package/src/commands/index.ts +1 -7
- package/src/commands/registry.ts +46 -12
- package/src/components/chat-composer.tsx +135 -39
- package/src/components/code-editor.tsx +28 -36
- package/src/components/command-palette.tsx +26 -5
- package/src/components/desktop-viewer.tsx +29 -16
- package/src/components/diff-view.tsx +27 -189
- package/src/components/enrollment-consent.tsx +28 -10
- package/src/components/enrollment-device-flow.tsx +8 -5
- package/src/components/file-browser.tsx +190 -56
- package/src/components/fleet-tile.tsx +13 -4
- package/src/components/machine-card.tsx +16 -4
- package/src/components/machine-dock-bar.tsx +20 -8
- package/src/components/machine-metrics.tsx +31 -8
- package/src/components/machine-status-pill.tsx +26 -5
- package/src/components/machines-dashboard.tsx +8 -2
- package/src/components/markdown.tsx +39 -9
- package/src/components/message-timeline.tsx +633 -109
- package/src/components/pierre-diff.tsx +83 -15
- package/src/components/pierre-file.tsx +10 -9
- package/src/components/sandbox-files.tsx +110 -227
- package/src/components/sandbox-terminal.tsx +256 -88
- package/src/components/sandbox-workspace.tsx +823 -0
- package/src/components/session-status.tsx +28 -2
- package/src/components/workbench-changes.tsx +541 -0
- package/src/components/workspace-dock.tsx +85 -22
- package/src/hooks/internal.ts +5 -2
- package/src/hooks/use-available-models.ts +7 -2
- package/src/hooks/use-billing-usage.ts +4 -1
- package/src/hooks/use-codex-accounts.ts +74 -44
- package/src/hooks/use-composer.ts +57 -36
- package/src/hooks/use-environments.ts +92 -47
- package/src/hooks/use-file-attachments.ts +101 -55
- package/src/hooks/use-goal.ts +52 -16
- package/src/hooks/use-machine-chip.ts +134 -0
- package/src/hooks/use-machines.ts +34 -16
- package/src/hooks/use-packs.ts +24 -19
- package/src/hooks/use-relay-frame-stream.ts +5 -2
- package/src/hooks/use-rigs.ts +335 -0
- package/src/hooks/use-sandbox-files.ts +213 -39
- package/src/hooks/use-sandbox-git.ts +188 -11
- package/src/hooks/use-sandbox-terminal.ts +18 -14
- package/src/hooks/use-scheduled-tasks.ts +10 -2
- package/src/hooks/use-session-capabilities.ts +77 -20
- package/src/hooks/use-session-control.ts +49 -21
- package/src/hooks/use-session-events.ts +48 -20
- package/src/hooks/use-session-lineage.ts +119 -0
- package/src/hooks/use-session.ts +43 -28
- package/src/hooks/use-slash-commands.ts +6 -4
- package/src/hooks/use-turn-queue.ts +74 -147
- package/src/hooks/use-windowed-sections.ts +204 -0
- package/src/hooks/use-workspace-capture.ts +233 -0
- package/src/hooks/use-workspace-edit.ts +231 -0
- package/src/hooks/use-workspace-sessions.ts +48 -5
- package/src/hooks/use-workspaces.ts +18 -15
- package/src/index.ts +127 -23
- package/src/lib/format.ts +3 -3
- package/src/lib/xterm-renderer.ts +65 -0
- package/src/lib/xterm-theme.ts +224 -18
- package/src/machines.ts +13 -3
- package/src/provider.tsx +3 -1
- package/src/timeline/activity-rail.tsx +213 -54
- package/src/timeline/disclosure-context.tsx +12 -2
- package/src/timeline/index.ts +21 -2
- package/src/timeline/parsers.ts +44 -10
- package/src/timeline/projection.ts +388 -85
- package/src/timeline/shared.tsx +111 -24
- package/src/timeline/tool-diff.tsx +24 -20
- package/src/timeline/tool-renderers.tsx +98 -21
- package/src/timeline/turn-summary.tsx +76 -23
- package/src/timeline/types.ts +96 -12
- package/styles/tokens.css +2 -2
- package/dist/chunk-5TIXRCSI.js.map +0 -1
package/src/timeline/shared.tsx
CHANGED
|
@@ -200,7 +200,11 @@ export function ActivityDisclosure({
|
|
|
200
200
|
const dataStatus = cancelled ? "cancelled" : failed ? "failed" : undefined;
|
|
201
201
|
|
|
202
202
|
if (!hasBody) {
|
|
203
|
-
return
|
|
203
|
+
return (
|
|
204
|
+
<div className={cn(rowClass, "cursor-default")} data-status={dataStatus}>
|
|
205
|
+
{inner}
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
return (
|
|
@@ -270,6 +274,7 @@ export function TermBlock({
|
|
|
270
274
|
output,
|
|
271
275
|
live,
|
|
272
276
|
tailLines = 12,
|
|
277
|
+
failed,
|
|
273
278
|
}: {
|
|
274
279
|
/**
|
|
275
280
|
* The command shown in the prompt header. Pass `null` when the row title
|
|
@@ -282,6 +287,8 @@ export function TermBlock({
|
|
|
282
287
|
/** The FULL output. TermBlock owns the tail/full slicing internally. */
|
|
283
288
|
output: string;
|
|
284
289
|
live?: boolean | undefined;
|
|
290
|
+
/** A non-zero exit / failed call — tints the left accent red (the one hue). */
|
|
291
|
+
failed?: boolean | undefined;
|
|
285
292
|
/**
|
|
286
293
|
* When the output exceeds the tail window, only the last `tailLines` are shown
|
|
287
294
|
* with a "show full output" toggle. The component holds the full text, so the
|
|
@@ -297,32 +304,51 @@ export function TermBlock({
|
|
|
297
304
|
const showMore = big && !full;
|
|
298
305
|
const showHeader = command != null || workdir != null;
|
|
299
306
|
|
|
307
|
+
// No frame, no fill: a quiet monospace run flush on the page, marked only by a
|
|
308
|
+
// 2px left accent so it reads as terminal output at a glance without becoming
|
|
309
|
+
// yet another nested box. Color is spent only on the exception — a settled run
|
|
310
|
+
// gets a calm neutral rule, a live one the running hue, a failed one the red.
|
|
300
311
|
return (
|
|
301
|
-
<div
|
|
312
|
+
<div
|
|
313
|
+
className={cn(
|
|
314
|
+
"min-w-0 border-l-2 pl-3",
|
|
315
|
+
failed
|
|
316
|
+
? "border-og-status-failed/50"
|
|
317
|
+
: live
|
|
318
|
+
? "border-og-status-running/50"
|
|
319
|
+
: "border-og-border",
|
|
320
|
+
)}
|
|
321
|
+
>
|
|
302
322
|
{showHeader ? (
|
|
303
|
-
<div className="flex items-center gap-2
|
|
323
|
+
<div className="flex items-center gap-2 pb-1">
|
|
304
324
|
<span className="select-none text-og-status-idle">$</span>
|
|
305
325
|
{command != null ? (
|
|
306
|
-
<span className="min-w-0 flex-1 truncate font-og-mono text-og-sm text-og-fg-muted">
|
|
326
|
+
<span className="min-w-0 flex-1 truncate font-og-mono text-og-sm text-og-fg-muted">
|
|
327
|
+
{command}
|
|
328
|
+
</span>
|
|
307
329
|
) : (
|
|
308
330
|
<span className="flex-1" />
|
|
309
331
|
)}
|
|
310
|
-
{workdir ?
|
|
332
|
+
{workdir ? (
|
|
333
|
+
<span className="shrink-0 font-og-mono text-og-xs text-og-fg-subtle">{workdir}</span>
|
|
334
|
+
) : null}
|
|
311
335
|
</div>
|
|
312
336
|
) : null}
|
|
313
337
|
{empty ? (
|
|
314
|
-
<p className="
|
|
338
|
+
<p className="font-og-mono text-og-xs italic text-og-fg-subtle">(no output)</p>
|
|
315
339
|
) : (
|
|
316
|
-
<pre className="max-h-72 overflow-auto whitespace-pre-wrap break-all
|
|
340
|
+
<pre className="max-h-72 overflow-auto whitespace-pre-wrap break-all font-og-mono text-og-xs leading-5 text-og-fg-muted">
|
|
317
341
|
{shown}
|
|
318
|
-
{live ?
|
|
342
|
+
{live ? (
|
|
343
|
+
<span className="ml-px inline-block h-[1em] w-[2px] translate-y-[2px] animate-og-blink bg-og-accent align-middle" />
|
|
344
|
+
) : null}
|
|
319
345
|
</pre>
|
|
320
346
|
)}
|
|
321
347
|
{showMore ? (
|
|
322
348
|
<button
|
|
323
349
|
type="button"
|
|
324
350
|
onClick={() => setFull(true)}
|
|
325
|
-
className="
|
|
351
|
+
className="mt-1 text-left text-og-xs text-og-fg-subtle transition-colors hover:text-og-fg"
|
|
326
352
|
>
|
|
327
353
|
show full output ({lines.length} lines)
|
|
328
354
|
</button>
|
|
@@ -333,20 +359,36 @@ export function TermBlock({
|
|
|
333
359
|
|
|
334
360
|
/* --- generic payload block -------------------------------------------------- */
|
|
335
361
|
|
|
336
|
-
export function PayloadBlock({
|
|
362
|
+
export function PayloadBlock({
|
|
363
|
+
label,
|
|
364
|
+
value,
|
|
365
|
+
failed,
|
|
366
|
+
}: {
|
|
367
|
+
label: string;
|
|
368
|
+
value: unknown;
|
|
369
|
+
failed?: boolean | undefined;
|
|
370
|
+
}) {
|
|
337
371
|
const text = typeof value === "string" ? value : stringifyPayload(value);
|
|
338
372
|
if (!text || text.trim() === "") {
|
|
339
373
|
return null;
|
|
340
374
|
}
|
|
375
|
+
// Flush on the page, no frame — a labelled monospace run marked only by a 2px
|
|
376
|
+
// left accent (red when the call failed, otherwise a calm neutral rule), so a
|
|
377
|
+
// payload reads as detail hanging off the row, never a nested card.
|
|
341
378
|
return (
|
|
342
|
-
<div
|
|
343
|
-
|
|
379
|
+
<div
|
|
380
|
+
className={cn(
|
|
381
|
+
"min-w-0 border-l-2 pl-3",
|
|
382
|
+
failed ? "border-og-status-failed/50" : "border-og-border",
|
|
383
|
+
)}
|
|
384
|
+
>
|
|
385
|
+
<p className="mb-1 text-og-xs font-medium uppercase tracking-[0.08em] text-og-fg-subtle">
|
|
386
|
+
{label}
|
|
387
|
+
</p>
|
|
344
388
|
<pre
|
|
345
389
|
className={cn(
|
|
346
|
-
"max-h-64 overflow-auto whitespace-pre-wrap break-all
|
|
347
|
-
failed
|
|
348
|
-
? "border-og-status-failed/30 bg-og-status-failed/5 text-og-status-failed"
|
|
349
|
-
: "border-og-border bg-og-bg/60 text-og-fg-muted",
|
|
390
|
+
"max-h-64 overflow-auto whitespace-pre-wrap break-all font-og-mono text-og-xs leading-5",
|
|
391
|
+
failed ? "text-og-status-failed" : "text-og-fg-muted",
|
|
350
392
|
)}
|
|
351
393
|
>
|
|
352
394
|
{text}
|
|
@@ -356,10 +398,18 @@ export function PayloadBlock({ label, value, failed }: { label: string; value: u
|
|
|
356
398
|
}
|
|
357
399
|
|
|
358
400
|
/** A quiet inline note inside an expanded body (lost output, empty frame, …). */
|
|
359
|
-
export function BodyNote({
|
|
401
|
+
export function BodyNote({
|
|
402
|
+
children,
|
|
403
|
+
tone,
|
|
404
|
+
}: {
|
|
405
|
+
children: ReactNode;
|
|
406
|
+
tone?: "error" | "muted" | undefined;
|
|
407
|
+
}) {
|
|
360
408
|
if (tone === "error") {
|
|
409
|
+
// A quiet error run marked by a 2px red accent — the same flush, frameless
|
|
410
|
+
// language as the payload/output blocks, not a filled callout box.
|
|
361
411
|
return (
|
|
362
|
-
<div className="
|
|
412
|
+
<div className="border-l-2 border-og-status-failed/50 pl-3 font-og-mono text-og-xs leading-5 text-og-status-failed">
|
|
363
413
|
{children}
|
|
364
414
|
</div>
|
|
365
415
|
);
|
|
@@ -383,7 +433,12 @@ const MEDIA_BOX = "h-7 w-[52px] shrink-0 rounded-og-xs border border-og-border";
|
|
|
383
433
|
*/
|
|
384
434
|
export function MediaSkeleton() {
|
|
385
435
|
return (
|
|
386
|
-
<span
|
|
436
|
+
<span
|
|
437
|
+
className={cn(
|
|
438
|
+
MEDIA_BOX,
|
|
439
|
+
"relative inline-flex items-center justify-center overflow-hidden bg-og-surface-2",
|
|
440
|
+
)}
|
|
441
|
+
>
|
|
387
442
|
<span className="absolute inset-0 animate-og-pulse bg-og-surface-3/50" />
|
|
388
443
|
<CameraIcon className="relative size-3.5 text-og-fg-subtle" />
|
|
389
444
|
</span>
|
|
@@ -406,7 +461,15 @@ export function MediaEmpty() {
|
|
|
406
461
|
* Outside one it degrades to a plain, non-interactive image — never a dead
|
|
407
462
|
* "Expand" button that announces an action it cannot perform.
|
|
408
463
|
*/
|
|
409
|
-
export function Thumbnail({
|
|
464
|
+
export function Thumbnail({
|
|
465
|
+
src,
|
|
466
|
+
caption,
|
|
467
|
+
alt = "screenshot",
|
|
468
|
+
}: {
|
|
469
|
+
src: string;
|
|
470
|
+
caption?: string | undefined;
|
|
471
|
+
alt?: string;
|
|
472
|
+
}) {
|
|
410
473
|
const lightbox = useLightboxOptional();
|
|
411
474
|
const [failed, setFailed] = useState(false);
|
|
412
475
|
if (failed) {
|
|
@@ -458,13 +521,28 @@ export function Thumbnail({ src, caption, alt = "screenshot" }: { src: string; c
|
|
|
458
521
|
* so it never breaks the row layout. Like {@link Thumbnail}, it degrades to a
|
|
459
522
|
* plain image outside a `LightboxProvider`.
|
|
460
523
|
*/
|
|
461
|
-
export function ScreenshotFigure({
|
|
524
|
+
export function ScreenshotFigure({
|
|
525
|
+
src,
|
|
526
|
+
caption,
|
|
527
|
+
alt = "screenshot",
|
|
528
|
+
}: {
|
|
529
|
+
src: string;
|
|
530
|
+
caption?: string | undefined;
|
|
531
|
+
alt?: string;
|
|
532
|
+
}) {
|
|
462
533
|
const lightbox = useLightboxOptional();
|
|
463
534
|
const [failed, setFailed] = useState(false);
|
|
464
535
|
const surface = "block w-full overflow-hidden rounded-og-md border border-og-border bg-og-bg";
|
|
465
536
|
// A plain <img>, like {@link Thumbnail} — a framework-agnostic SDK has no host
|
|
466
537
|
// Image component to defer to.
|
|
467
|
-
const img =
|
|
538
|
+
const img = (
|
|
539
|
+
<img
|
|
540
|
+
src={src}
|
|
541
|
+
alt={alt}
|
|
542
|
+
onError={() => setFailed(true)}
|
|
543
|
+
className="max-h-80 w-full object-contain"
|
|
544
|
+
/>
|
|
545
|
+
);
|
|
468
546
|
return (
|
|
469
547
|
<figure className="m-0 min-w-0">
|
|
470
548
|
{failed ? (
|
|
@@ -472,13 +550,22 @@ export function ScreenshotFigure({ src, caption, alt = "screenshot" }: { src: st
|
|
|
472
550
|
image unavailable
|
|
473
551
|
</div>
|
|
474
552
|
) : lightbox ? (
|
|
475
|
-
<button
|
|
553
|
+
<button
|
|
554
|
+
type="button"
|
|
555
|
+
onClick={() => lightbox.open(src, caption)}
|
|
556
|
+
className={surface}
|
|
557
|
+
aria-label="Expand screenshot"
|
|
558
|
+
>
|
|
476
559
|
{img}
|
|
477
560
|
</button>
|
|
478
561
|
) : (
|
|
479
562
|
<div className={surface}>{img}</div>
|
|
480
563
|
)}
|
|
481
|
-
{caption ?
|
|
564
|
+
{caption ? (
|
|
565
|
+
<figcaption className="mt-1.5 font-og-mono text-og-xs text-og-fg-subtle">
|
|
566
|
+
{caption}
|
|
567
|
+
</figcaption>
|
|
568
|
+
) : null}
|
|
482
569
|
</figure>
|
|
483
570
|
);
|
|
484
571
|
}
|
|
@@ -2,18 +2,16 @@ import type { GitFileDiff } from "@opengeni/sdk";
|
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
4
|
import { useThemeType } from "../lib/use-theme-type";
|
|
5
|
-
import { DiffView } from "../components/diff-view";
|
|
6
5
|
import { PierreDiff } from "../components/pierre-diff";
|
|
7
6
|
|
|
8
7
|
/* ----------------------------------------------------------------------------
|
|
9
8
|
Tool diff
|
|
10
9
|
|
|
11
10
|
Renders parsed `GitFileDiff[]` (from the V4A apply_patch parser) through the
|
|
12
|
-
EXACT same diff stack the Files
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
toggle sits in the header.
|
|
11
|
+
EXACT same diff stack the Files/Changes tabs use: `PierreDiff` (Shiki-
|
|
12
|
+
highlighted), with a built-in plain-text degrade for a host without
|
|
13
|
+
`@pierre/diffs` — one renderer, a single data contract. A per-block
|
|
14
|
+
Unified/Split toggle sits in the header.
|
|
17
15
|
-------------------------------------------------------------------------- */
|
|
18
16
|
|
|
19
17
|
export function ToolDiff({ files }: { files: GitFileDiff[] }) {
|
|
@@ -22,25 +20,23 @@ export function ToolDiff({ files }: { files: GitFileDiff[] }) {
|
|
|
22
20
|
// the Files tab uses, then thread it into Pierre. Without it Pierre falls back
|
|
23
21
|
// to its hard dark default and renders a github-dark slab inside a light page.
|
|
24
22
|
const themeType = useThemeType(undefined);
|
|
25
|
-
// The fallback chain reads top-down: try Pierre's Shiki renderer, and if it's
|
|
26
|
-
// not installed fall back to the built-in DiffView. The plain node is named
|
|
27
|
-
// once and reused, so the same props aren't threaded through three JSX nodes.
|
|
28
|
-
const plain = <DiffView diff={files} layout={layout} />;
|
|
29
23
|
return (
|
|
30
24
|
<div className="min-w-0">
|
|
31
25
|
<div className="mb-1.5 flex justify-end">
|
|
32
26
|
<LayoutToggle layout={layout} onChange={setLayout} />
|
|
33
27
|
</div>
|
|
34
|
-
<
|
|
35
|
-
diff={files}
|
|
36
|
-
layout={layout}
|
|
37
|
-
fallback={<PierreDiff diff={files} layout={layout} themeType={themeType} fallback={plain} />}
|
|
38
|
-
/>
|
|
28
|
+
<PierreDiff diff={files} layout={layout} themeType={themeType} />
|
|
39
29
|
</div>
|
|
40
30
|
);
|
|
41
31
|
}
|
|
42
32
|
|
|
43
|
-
function LayoutToggle({
|
|
33
|
+
function LayoutToggle({
|
|
34
|
+
layout,
|
|
35
|
+
onChange,
|
|
36
|
+
}: {
|
|
37
|
+
layout: "unified" | "split";
|
|
38
|
+
onChange: (next: "unified" | "split") => void;
|
|
39
|
+
}) {
|
|
44
40
|
return (
|
|
45
41
|
<div className="inline-flex items-center gap-px rounded-og-xs border border-og-border p-px">
|
|
46
42
|
{(["unified", "split"] as const).map((value) => (
|
|
@@ -50,7 +46,9 @@ function LayoutToggle({ layout, onChange }: { layout: "unified" | "split"; onCha
|
|
|
50
46
|
onClick={() => onChange(value)}
|
|
51
47
|
className={cn(
|
|
52
48
|
"rounded-og-xs px-2 py-[3px] text-og-xs font-medium capitalize transition-colors",
|
|
53
|
-
layout === value
|
|
49
|
+
layout === value
|
|
50
|
+
? "bg-og-surface-2 text-og-fg"
|
|
51
|
+
: "text-og-fg-subtle hover:text-og-fg-muted",
|
|
54
52
|
)}
|
|
55
53
|
>
|
|
56
54
|
{value}
|
|
@@ -62,15 +60,21 @@ function LayoutToggle({ layout, onChange }: { layout: "unified" | "split"; onCha
|
|
|
62
60
|
|
|
63
61
|
/** Raw-patch fallback for a V4A hunk string the parser could not structure. */
|
|
64
62
|
export function RawPatch({ diff }: { diff: string }) {
|
|
63
|
+
const occurrences = new Map<string, number>();
|
|
64
|
+
const lines = diff.split("\n").map((line) => {
|
|
65
|
+
const occurrence = (occurrences.get(line) ?? 0) + 1;
|
|
66
|
+
occurrences.set(line, occurrence);
|
|
67
|
+
return { key: `${line}\u0000${occurrence}`, line };
|
|
68
|
+
});
|
|
65
69
|
return (
|
|
66
70
|
<div className="min-w-0">
|
|
67
71
|
<p className="mb-1 text-og-xs font-medium uppercase tracking-[0.08em] text-og-fg-subtle">
|
|
68
72
|
raw patch (could not parse hunks)
|
|
69
73
|
</p>
|
|
70
|
-
<pre className="max-h-72 overflow-auto
|
|
71
|
-
{
|
|
74
|
+
<pre className="max-h-72 overflow-auto border-l-2 border-og-border pl-3 font-og-mono text-og-xs leading-5">
|
|
75
|
+
{lines.map(({ key, line }) => (
|
|
72
76
|
<span
|
|
73
|
-
key={
|
|
77
|
+
key={key}
|
|
74
78
|
className={cn(
|
|
75
79
|
"block",
|
|
76
80
|
line.startsWith("@@")
|
|
@@ -33,7 +33,12 @@ import {
|
|
|
33
33
|
screenshotDataUrl,
|
|
34
34
|
type ApplyPatchOperation,
|
|
35
35
|
} from "./parsers";
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
createToolRegistry,
|
|
38
|
+
type ToolRegistry,
|
|
39
|
+
type ToolRegistryEntry,
|
|
40
|
+
type ToolRendererProps,
|
|
41
|
+
} from "./registry";
|
|
37
42
|
import {
|
|
38
43
|
BodyNote,
|
|
39
44
|
MediaEmpty,
|
|
@@ -101,8 +106,8 @@ function ExecRenderer({ item }: ToolRendererProps) {
|
|
|
101
106
|
preview="output lost — NUL byte could not be stored"
|
|
102
107
|
>
|
|
103
108
|
<BodyNote tone="error">
|
|
104
|
-
output contained a NUL byte and could not be stored; the turn failed on this tool's
|
|
105
|
-
event ever arrived.
|
|
109
|
+
output contained a NUL byte and could not be stored; the turn failed on this tool's
|
|
110
|
+
output insert — no output event ever arrived.
|
|
106
111
|
</BodyNote>
|
|
107
112
|
</ActivityDisclosure>
|
|
108
113
|
);
|
|
@@ -135,7 +140,11 @@ function ExecRenderer({ item }: ToolRendererProps) {
|
|
|
135
140
|
title={title}
|
|
136
141
|
titleMono
|
|
137
142
|
running
|
|
138
|
-
preview={
|
|
143
|
+
preview={
|
|
144
|
+
<RunningPreview>
|
|
145
|
+
{streamed ? `${streamed.split("\n").length} lines` : "running…"}
|
|
146
|
+
</RunningPreview>
|
|
147
|
+
}
|
|
139
148
|
>
|
|
140
149
|
{/* The row title is already `$ ${cmd}`; the TermBlock header drops the
|
|
141
150
|
command (command={null}) so it never repeats above the output. */}
|
|
@@ -178,7 +187,12 @@ function ExecRenderer({ item }: ToolRendererProps) {
|
|
|
178
187
|
cancelled={item.status === "cancelled"}
|
|
179
188
|
preview={truncated ? `⋯ truncated · ${preview}` : preview}
|
|
180
189
|
>
|
|
181
|
-
<TermBlock
|
|
190
|
+
<TermBlock
|
|
191
|
+
command={null}
|
|
192
|
+
workdir={workdir}
|
|
193
|
+
output={body}
|
|
194
|
+
failed={item.status === "failed" || (exitCode != null && exitCode !== 0)}
|
|
195
|
+
/>
|
|
182
196
|
{bgSession != null ? (
|
|
183
197
|
<BodyNote>↳ session {bgSession} — a later write_stdin can target this PTY.</BodyNote>
|
|
184
198
|
) : null}
|
|
@@ -190,7 +204,10 @@ function ExecRenderer({ item }: ToolRendererProps) {
|
|
|
190
204
|
|
|
191
205
|
function WriteStdinRenderer({ item }: ToolRendererProps) {
|
|
192
206
|
const args = parseToolArgs(item.arguments);
|
|
193
|
-
const sessionId =
|
|
207
|
+
const sessionId =
|
|
208
|
+
typeof args.session_id === "string" || typeof args.session_id === "number"
|
|
209
|
+
? args.session_id
|
|
210
|
+
: undefined;
|
|
194
211
|
const running = item.status === "running";
|
|
195
212
|
const text = typeof item.output === "string" ? item.output : stringifyPayload(item.output);
|
|
196
213
|
const lost = isExecSessionLostBanner(text);
|
|
@@ -248,7 +265,13 @@ function verbForOp(op: ApplyPatchOperation | undefined): string {
|
|
|
248
265
|
if (!op) {
|
|
249
266
|
return "Edited";
|
|
250
267
|
}
|
|
251
|
-
return op.type === "create_file"
|
|
268
|
+
return op.type === "create_file"
|
|
269
|
+
? "Created"
|
|
270
|
+
: op.type === "delete_file"
|
|
271
|
+
? "Deleted"
|
|
272
|
+
: op.moveTo
|
|
273
|
+
? "Renamed"
|
|
274
|
+
: "Edited";
|
|
252
275
|
}
|
|
253
276
|
|
|
254
277
|
function basename(path: string): string {
|
|
@@ -267,7 +290,15 @@ function dirname(path: string): string {
|
|
|
267
290
|
* for the expanded DiffView gutter, so the one-line rail stays a calm, single
|
|
268
291
|
* hue (the file path) with no competing colored numerics.
|
|
269
292
|
*/
|
|
270
|
-
function PathPreview({
|
|
293
|
+
function PathPreview({
|
|
294
|
+
path,
|
|
295
|
+
add,
|
|
296
|
+
del,
|
|
297
|
+
}: {
|
|
298
|
+
path: string;
|
|
299
|
+
add?: number | undefined;
|
|
300
|
+
del?: number | undefined;
|
|
301
|
+
}) {
|
|
271
302
|
return (
|
|
272
303
|
<span className="inline-flex items-center gap-2 truncate font-og-mono">
|
|
273
304
|
<span className="truncate">
|
|
@@ -303,7 +334,11 @@ function ApplyPatchRenderer({ item }: ToolRendererProps) {
|
|
|
303
334
|
iconTone="running"
|
|
304
335
|
title={fileCount > 1 ? `Applying ${fileCount} files` : titleVerb}
|
|
305
336
|
running
|
|
306
|
-
preview={
|
|
337
|
+
preview={
|
|
338
|
+
<RunningPreview>
|
|
339
|
+
{fileCount > 1 ? `${fileCount} files` : firstOp ? firstOp.path : "applying…"}
|
|
340
|
+
</RunningPreview>
|
|
341
|
+
}
|
|
307
342
|
>
|
|
308
343
|
{ops.map((op) => {
|
|
309
344
|
const file = safeParseOp(op);
|
|
@@ -478,7 +513,6 @@ function computerVerb(action: ComputerAction | undefined): string {
|
|
|
478
513
|
}
|
|
479
514
|
}
|
|
480
515
|
|
|
481
|
-
|
|
482
516
|
/** Coerce a function-tool arguments payload into the ComputerAction fields. */
|
|
483
517
|
function asComputerArgs(args: unknown): Partial<ComputerAction> {
|
|
484
518
|
if (!args) {
|
|
@@ -511,7 +545,7 @@ function ComputerCallRenderer({ item }: ToolRendererProps) {
|
|
|
511
545
|
// every transport.
|
|
512
546
|
const functionAction: ComputerAction | undefined =
|
|
513
547
|
!raw.action && item.name.startsWith("computer_") && item.name !== "computer_call"
|
|
514
|
-
? { type: item.name.slice("computer_".length), ...
|
|
548
|
+
? { type: item.name.slice("computer_".length), ...asComputerArgs(item.arguments) }
|
|
515
549
|
: undefined;
|
|
516
550
|
const action = raw.action ?? functionAction;
|
|
517
551
|
const actions = raw.actions ?? (action ? [action] : []);
|
|
@@ -531,7 +565,13 @@ function ComputerCallRenderer({ item }: ToolRendererProps) {
|
|
|
531
565
|
if (running) {
|
|
532
566
|
return (
|
|
533
567
|
<ActivityDisclosure
|
|
534
|
-
icon={
|
|
568
|
+
icon={
|
|
569
|
+
isShot ? (
|
|
570
|
+
<CameraIcon className={ICON_SIZE} />
|
|
571
|
+
) : (
|
|
572
|
+
<MousePointer2Icon className={ICON_SIZE} />
|
|
573
|
+
)
|
|
574
|
+
}
|
|
535
575
|
iconTone="running"
|
|
536
576
|
title={verb}
|
|
537
577
|
running
|
|
@@ -576,7 +616,13 @@ function ComputerCallRenderer({ item }: ToolRendererProps) {
|
|
|
576
616
|
const caption = `${verb}${actions.length > 1 ? ` (+${actions.length - 1} more)` : ""}`;
|
|
577
617
|
return (
|
|
578
618
|
<ActivityDisclosure
|
|
579
|
-
icon={
|
|
619
|
+
icon={
|
|
620
|
+
isShot ? (
|
|
621
|
+
<CameraIcon className={ICON_SIZE} />
|
|
622
|
+
) : (
|
|
623
|
+
<MousePointer2Icon className={ICON_SIZE} />
|
|
624
|
+
)
|
|
625
|
+
}
|
|
580
626
|
iconTone={isFailed ? "failed" : "accent"}
|
|
581
627
|
title={`${verb}${countSuffix}`}
|
|
582
628
|
failed={isFailed}
|
|
@@ -599,7 +645,13 @@ function ComputerCallRenderer({ item }: ToolRendererProps) {
|
|
|
599
645
|
cancelled={isCancelled}
|
|
600
646
|
media={<MediaEmpty />}
|
|
601
647
|
>
|
|
602
|
-
<BodyNote>
|
|
648
|
+
<BodyNote>
|
|
649
|
+
{isFailed
|
|
650
|
+
? "computer_call failed — no image returned."
|
|
651
|
+
: isCancelled
|
|
652
|
+
? "computer_call interrupted — no image returned."
|
|
653
|
+
: "(no image) — the session returned an empty screenshot."}
|
|
654
|
+
</BodyNote>
|
|
603
655
|
</ActivityDisclosure>
|
|
604
656
|
);
|
|
605
657
|
}
|
|
@@ -625,7 +677,9 @@ function ComputerCallRenderer({ item }: ToolRendererProps) {
|
|
|
625
677
|
type WebSearchResult = { title: string; domain: string; snippet: string };
|
|
626
678
|
|
|
627
679
|
function WebSearchRenderer({ item }: ToolRendererProps) {
|
|
628
|
-
const raw = (item.raw ?? {}) as {
|
|
680
|
+
const raw = (item.raw ?? {}) as {
|
|
681
|
+
providerData?: { action?: { query?: string; queries?: string[] } };
|
|
682
|
+
};
|
|
629
683
|
const action = raw.providerData?.action ?? {};
|
|
630
684
|
const query = action.query ?? "(query unavailable)";
|
|
631
685
|
const queries = action.queries ?? [];
|
|
@@ -638,6 +692,13 @@ function WebSearchRenderer({ item }: ToolRendererProps) {
|
|
|
638
692
|
const results = Array.isArray(rawResults)
|
|
639
693
|
? (rawResults as unknown[]).filter((r): r is WebSearchResult => !!r && typeof r === "object")
|
|
640
694
|
: undefined;
|
|
695
|
+
const resultOccurrences = new Map<string, number>();
|
|
696
|
+
const keyedResults = results?.map((result) => {
|
|
697
|
+
const contentKey = `${result.domain}\u0000${result.title}\u0000${result.snippet}`;
|
|
698
|
+
const occurrence = (resultOccurrences.get(contentKey) ?? 0) + 1;
|
|
699
|
+
resultOccurrences.set(contentKey, occurrence);
|
|
700
|
+
return { key: `${contentKey}\u0000${occurrence}`, result };
|
|
701
|
+
});
|
|
641
702
|
|
|
642
703
|
if (running) {
|
|
643
704
|
return (
|
|
@@ -662,10 +723,10 @@ function WebSearchRenderer({ item }: ToolRendererProps) {
|
|
|
662
723
|
failed={item.status === "failed"}
|
|
663
724
|
cancelled={item.status === "cancelled"}
|
|
664
725
|
>
|
|
665
|
-
{
|
|
726
|
+
{keyedResults && keyedResults.length ? (
|
|
666
727
|
<ul className="flex flex-col gap-2">
|
|
667
|
-
{
|
|
668
|
-
<li key={
|
|
728
|
+
{keyedResults.map(({ key, result }) => (
|
|
729
|
+
<li key={key} className="flex gap-2.5">
|
|
669
730
|
<GlobeIcon className="mt-0.5 size-3.5 shrink-0 text-og-fg-subtle" />
|
|
670
731
|
<div className="min-w-0">
|
|
671
732
|
<p className="truncate text-og-base text-og-fg">
|
|
@@ -685,7 +746,13 @@ function WebSearchRenderer({ item }: ToolRendererProps) {
|
|
|
685
746
|
|
|
686
747
|
/* ---- view_image ------------------------------------------------------------ */
|
|
687
748
|
|
|
688
|
-
const VIEW_IMAGE_ERRORS = [
|
|
749
|
+
const VIEW_IMAGE_ERRORS = [
|
|
750
|
+
"was not found",
|
|
751
|
+
"is not a file",
|
|
752
|
+
"exceeded the allowed size",
|
|
753
|
+
"is not a supported image",
|
|
754
|
+
"unable to read image",
|
|
755
|
+
];
|
|
689
756
|
|
|
690
757
|
function ViewImageRenderer({ item }: ToolRendererProps) {
|
|
691
758
|
const args = parseToolArgs(item.arguments);
|
|
@@ -750,7 +817,13 @@ function ViewImageRenderer({ item }: ToolRendererProps) {
|
|
|
750
817
|
cancelled={viewCancelled}
|
|
751
818
|
preview="(no image)"
|
|
752
819
|
>
|
|
753
|
-
<BodyNote>
|
|
820
|
+
<BodyNote>
|
|
821
|
+
{viewFailed
|
|
822
|
+
? "view_image failed — no image data returned."
|
|
823
|
+
: viewCancelled
|
|
824
|
+
? "view_image interrupted."
|
|
825
|
+
: "(no image) — the sandbox session returned no image data."}
|
|
826
|
+
</BodyNote>
|
|
754
827
|
</ActivityDisclosure>
|
|
755
828
|
);
|
|
756
829
|
}
|
|
@@ -802,7 +875,11 @@ function SecretSetRenderer({ item }: ToolRendererProps) {
|
|
|
802
875
|
preview={errorText ?? "variable write failed"}
|
|
803
876
|
>
|
|
804
877
|
<PayloadBlock label="Arguments" value={redactSecrets(args)} />
|
|
805
|
-
{errorText ?
|
|
878
|
+
{errorText ? (
|
|
879
|
+
<PayloadBlock label="Error" value={errorText} failed />
|
|
880
|
+
) : (
|
|
881
|
+
<BodyNote tone="error">the tool call failed with no output.</BodyNote>
|
|
882
|
+
)}
|
|
806
883
|
</ActivityDisclosure>
|
|
807
884
|
);
|
|
808
885
|
}
|