@opengeni/react 0.12.0 → 0.15.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 +20 -14
- package/dist/chunk-TOJR776I.js +2280 -0
- package/dist/chunk-TOJR776I.js.map +1 -0
- package/dist/index.d.ts +1040 -363
- package/dist/index.js +10938 -6281
- package/dist/index.js.map +1 -1
- package/dist/{machines-BeZ3bD3t.d.ts → machines-BpdwuQcD.d.ts} +134 -14
- package/dist/machines.d.ts +1 -1
- package/dist/machines.js +23 -1
- package/package.json +20 -12
- package/src/approvals.ts +16 -4
- package/src/client.ts +41 -5
- package/src/commands/index.ts +1 -7
- package/src/commands/registry.ts +46 -12
- package/src/components/chat-composer.tsx +417 -69
- 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 +93 -15
- package/src/components/machine-dock-bar.tsx +20 -8
- package/src/components/machine-health-pill.tsx +68 -0
- package/src/components/machine-metrics.tsx +28 -19
- package/src/components/machine-status-pill.tsx +26 -5
- package/src/components/machines/health.ts +146 -0
- package/src/components/machines/machine-detail.tsx +220 -0
- package/src/components/machines/metric-history-chart.tsx +298 -0
- package/src/components/machines/metric-sparkline.tsx +76 -0
- package/src/components/machines/series.ts +113 -0
- package/src/components/machines-dashboard.tsx +21 -3
- 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/queue-surface.tsx +578 -0
- package/src/components/sandbox-files.tsx +193 -225
- package/src/components/sandbox-terminal.tsx +256 -88
- package/src/components/sandbox-workspace.tsx +957 -0
- package/src/components/session-status.tsx +22 -2
- package/src/components/workbench-changes.tsx +585 -0
- package/src/components/workspace-dock.tsx +213 -59
- 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 +414 -63
- 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 +50 -23
- package/src/hooks/use-session-events.ts +50 -21
- package/src/hooks/use-session-lineage.ts +128 -0
- package/src/hooks/use-session.ts +53 -30
- package/src/hooks/use-slash-commands.ts +6 -4
- package/src/hooks/use-turn-queue.ts +202 -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 +133 -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 +29 -3
- package/src/provider.tsx +195 -6
- 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 +87 -16
- package/src/timeline/projection.ts +412 -87
- 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/index.css +22 -0
- package/styles/tokens.css +2 -2
- package/dist/chunk-5TIXRCSI.js +0 -1291
- package/dist/chunk-5TIXRCSI.js.map +0 -1
|
@@ -106,6 +106,10 @@ export type CodeEditorProps = {
|
|
|
106
106
|
initialContents: string;
|
|
107
107
|
/** Persist the current buffer. Resolves when the write lands; rejects to surface an error. */
|
|
108
108
|
onSave: (contents: string) => Promise<unknown>;
|
|
109
|
+
/** Fired once when the buffer FIRST diverges from its baseline (the first real
|
|
110
|
+
* keystroke) — the wake-on-edit intent. The host warms the box on this so the
|
|
111
|
+
* eventual save lands fast; merely opening the file for reading never fires it. */
|
|
112
|
+
onEditIntent?: (() => void) | undefined;
|
|
109
113
|
/** Read-only mode (e.g. a truncated/too-large file shown for reference only). */
|
|
110
114
|
readOnly?: boolean | undefined;
|
|
111
115
|
themeType?: "dark" | "light" | undefined;
|
|
@@ -132,6 +136,7 @@ export function CodeEditor({
|
|
|
132
136
|
path,
|
|
133
137
|
initialContents,
|
|
134
138
|
onSave,
|
|
139
|
+
onEditIntent,
|
|
135
140
|
readOnly = false,
|
|
136
141
|
themeType = "dark",
|
|
137
142
|
loading,
|
|
@@ -155,18 +160,35 @@ export function CodeEditor({
|
|
|
155
160
|
// Reload the buffer when the file identity changes. Guard on initialContents
|
|
156
161
|
// too so an external refresh of the SAME path (e.g. fs.changed re-read) reseeds
|
|
157
162
|
// a clean buffer — but only when the user hasn't got unsaved edits in flight.
|
|
163
|
+
// Fire the wake-on-edit intent at most once per opened file — reset on re-seed.
|
|
164
|
+
const editIntentFiredRef = useRef(false);
|
|
158
165
|
const lastSeed = useRef<{ path: string; contents: string }>({ path, contents: initialContents });
|
|
159
166
|
useEffect(() => {
|
|
160
167
|
const seedChanged =
|
|
161
168
|
lastSeed.current.path !== path || lastSeed.current.contents !== initialContents;
|
|
162
169
|
if (!seedChanged) return;
|
|
163
170
|
lastSeed.current = { path, contents: initialContents };
|
|
171
|
+
editIntentFiredRef.current = false;
|
|
164
172
|
setValue(initialContents);
|
|
165
173
|
setBaseline(initialContents);
|
|
166
174
|
setSaveError(null);
|
|
167
175
|
setSavedTick(false);
|
|
168
176
|
}, [path, initialContents]);
|
|
169
177
|
|
|
178
|
+
// Record a buffer change: the FIRST divergence from the baseline is the edit
|
|
179
|
+
// intent that warms the box (idempotent — latched per opened file).
|
|
180
|
+
const noteEdit = useCallback(
|
|
181
|
+
(next: string) => {
|
|
182
|
+
setValue(next);
|
|
183
|
+
setSavedTick(false);
|
|
184
|
+
if (!editIntentFiredRef.current && next !== baseline) {
|
|
185
|
+
editIntentFiredRef.current = true;
|
|
186
|
+
onEditIntent?.();
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
[baseline, onEditIntent],
|
|
190
|
+
);
|
|
191
|
+
|
|
170
192
|
// Resolve the lazy bundle (editor + keymap/Prec helpers + language grammar)
|
|
171
193
|
// once, re-resolving the grammar when the file's language class changes.
|
|
172
194
|
const langKey = useMemo(() => languageForPath(path), [path]);
|
|
@@ -257,16 +279,7 @@ export function CodeEditor({
|
|
|
257
279
|
if (failed) {
|
|
258
280
|
return (
|
|
259
281
|
<div className={cn("flex h-full min-h-0 flex-col", className)}>
|
|
260
|
-
{fallback ??
|
|
261
|
-
<PlainTextarea
|
|
262
|
-
value={value}
|
|
263
|
-
readOnly={readOnly}
|
|
264
|
-
onChange={(next) => {
|
|
265
|
-
setValue(next);
|
|
266
|
-
setSavedTick(false);
|
|
267
|
-
}}
|
|
268
|
-
/>
|
|
269
|
-
)}
|
|
282
|
+
{fallback ?? <PlainTextarea value={value} readOnly={readOnly} onChange={noteEdit} />}
|
|
270
283
|
</div>
|
|
271
284
|
);
|
|
272
285
|
}
|
|
@@ -282,11 +295,7 @@ export function CodeEditor({
|
|
|
282
295
|
<span
|
|
283
296
|
className={cn(
|
|
284
297
|
"size-1.5 shrink-0 rounded-full transition-colors",
|
|
285
|
-
readOnly
|
|
286
|
-
? "bg-transparent"
|
|
287
|
-
: dirty
|
|
288
|
-
? "bg-og-status-running"
|
|
289
|
-
: "bg-og-status-idle",
|
|
298
|
+
readOnly ? "bg-transparent" : dirty ? "bg-og-status-running" : "bg-og-status-idle",
|
|
290
299
|
)}
|
|
291
300
|
title={readOnly ? "read-only" : dirty ? "unsaved changes" : "saved"}
|
|
292
301
|
/>
|
|
@@ -303,15 +312,9 @@ export function CodeEditor({
|
|
|
303
312
|
{saveError.message}
|
|
304
313
|
</span>
|
|
305
314
|
)}
|
|
306
|
-
{!saveError && savedTick &&
|
|
307
|
-
<span className="text-og-xs text-og-status-idle">
|
|
308
|
-
Saved
|
|
309
|
-
</span>
|
|
310
|
-
)}
|
|
315
|
+
{!saveError && savedTick && <span className="text-og-xs text-og-status-idle">Saved</span>}
|
|
311
316
|
{readOnly ? (
|
|
312
|
-
<span className="text-og-xs uppercase tracking-wide text-og-fg-subtle">
|
|
313
|
-
Read-only
|
|
314
|
-
</span>
|
|
317
|
+
<span className="text-og-xs uppercase tracking-wide text-og-fg-subtle">Read-only</span>
|
|
315
318
|
) : (
|
|
316
319
|
<button
|
|
317
320
|
type="button"
|
|
@@ -348,14 +351,7 @@ export function CodeEditor({
|
|
|
348
351
|
extensions={cmExtensions}
|
|
349
352
|
height="100%"
|
|
350
353
|
className="og-cm-editor min-h-full text-og-sm"
|
|
351
|
-
onChange={
|
|
352
|
-
readOnly
|
|
353
|
-
? undefined
|
|
354
|
-
: (next: string) => {
|
|
355
|
-
setValue(next);
|
|
356
|
-
setSavedTick(false);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
354
|
+
onChange={readOnly ? undefined : noteEdit}
|
|
359
355
|
/>
|
|
360
356
|
</Suspense>
|
|
361
357
|
</div>
|
|
@@ -390,9 +386,5 @@ function PlainTextarea({
|
|
|
390
386
|
}
|
|
391
387
|
|
|
392
388
|
function EditorSkeleton() {
|
|
393
|
-
return
|
|
394
|
-
<div className="p-3 text-og-sm text-og-fg-subtle">
|
|
395
|
-
Loading editor…
|
|
396
|
-
</div>
|
|
397
|
-
);
|
|
389
|
+
return <div className="p-3 text-og-sm text-og-fg-subtle">Loading editor…</div>;
|
|
398
390
|
}
|
|
@@ -23,7 +23,15 @@ export type CommandPaletteProps = {
|
|
|
23
23
|
* the opengeni#46 design tokens. Full keyboard nav lives in useSlashCommands;
|
|
24
24
|
* this component is presentational + aria.
|
|
25
25
|
*/
|
|
26
|
-
export function CommandPalette({
|
|
26
|
+
export function CommandPalette({
|
|
27
|
+
open,
|
|
28
|
+
items,
|
|
29
|
+
highlight,
|
|
30
|
+
onHighlight,
|
|
31
|
+
onRun,
|
|
32
|
+
argHintText,
|
|
33
|
+
listboxId,
|
|
34
|
+
}: CommandPaletteProps) {
|
|
27
35
|
return (
|
|
28
36
|
<AnimatePresence>
|
|
29
37
|
{open ? (
|
|
@@ -61,14 +69,25 @@ export function CommandPalette({ open, items, highlight, onHighlight, onRun, arg
|
|
|
61
69
|
className={cn(
|
|
62
70
|
"mx-1 flex cursor-pointer items-center gap-2 rounded-og-md px-2.5 py-1.5",
|
|
63
71
|
"transition-colors duration-100",
|
|
64
|
-
selected
|
|
72
|
+
selected
|
|
73
|
+
? "bg-og-accent/15 text-og-fg"
|
|
74
|
+
: "text-og-fg-muted hover:bg-og-surface-3",
|
|
65
75
|
)}
|
|
66
76
|
>
|
|
67
77
|
<span className="flex min-w-0 items-baseline gap-1.5">
|
|
68
|
-
<span
|
|
78
|
+
<span
|
|
79
|
+
className={cn(
|
|
80
|
+
"font-mono text-[13px]",
|
|
81
|
+
selected ? "text-og-accent" : "text-og-fg",
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
69
84
|
/{command.name}
|
|
70
85
|
</span>
|
|
71
|
-
{hint ?
|
|
86
|
+
{hint ? (
|
|
87
|
+
<span className="truncate font-mono text-[11px] text-og-fg-subtle">
|
|
88
|
+
{hint}
|
|
89
|
+
</span>
|
|
90
|
+
) : null}
|
|
72
91
|
</span>
|
|
73
92
|
<span className="ml-auto flex items-center gap-1.5">
|
|
74
93
|
{command.danger ? (
|
|
@@ -76,7 +95,9 @@ export function CommandPalette({ open, items, highlight, onHighlight, onRun, arg
|
|
|
76
95
|
danger
|
|
77
96
|
</span>
|
|
78
97
|
) : null}
|
|
79
|
-
<span className="truncate text-[12px] text-og-fg-subtle max-sm:hidden">
|
|
98
|
+
<span className="truncate text-[12px] text-og-fg-subtle max-sm:hidden">
|
|
99
|
+
{command.description}
|
|
100
|
+
</span>
|
|
80
101
|
</span>
|
|
81
102
|
</li>
|
|
82
103
|
);
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
CapabilityUnavailableReason,
|
|
3
|
+
DesktopRfbFactory,
|
|
4
|
+
DesktopStreamCapability,
|
|
5
|
+
} from "@opengeni/sdk";
|
|
2
6
|
import { LoaderCircleIcon, MonitorIcon, MousePointerClickIcon, WifiOffIcon } from "lucide-react";
|
|
3
7
|
import { type ReactNode, useEffect, useRef, useState } from "react";
|
|
4
8
|
import { cn } from "../lib/cn";
|
|
@@ -214,8 +218,7 @@ export function DesktopViewer({
|
|
|
214
218
|
// Do NOT open a socket while the viewer-cap (429) notice is showing — the slot
|
|
215
219
|
// is already exhausted, so connecting would only burn a doomed attempt (and in
|
|
216
220
|
// tests leak an unhandled ws error from the never-resolving tunnel URL).
|
|
217
|
-
const connectCapability =
|
|
218
|
-
!transportNull && !needsAck && !viewerCapReached ? capability : null;
|
|
221
|
+
const connectCapability = !transportNull && !needsAck && !viewerCapReached ? capability : null;
|
|
219
222
|
const stream = useDesktopStream({
|
|
220
223
|
capability: connectCapability,
|
|
221
224
|
containerRef,
|
|
@@ -309,7 +312,14 @@ export function DesktopViewer({
|
|
|
309
312
|
if (!hasLiveUrl || connected || stream.error || connectTimeoutMs <= 0) return;
|
|
310
313
|
const timer = setTimeout(() => setConnectTimedOut(true), connectTimeoutMs);
|
|
311
314
|
return () => clearTimeout(timer);
|
|
312
|
-
}, [
|
|
315
|
+
}, [
|
|
316
|
+
hasLiveUrl,
|
|
317
|
+
connected,
|
|
318
|
+
connectTimeoutMs,
|
|
319
|
+
connectCapability?.url,
|
|
320
|
+
stream.error,
|
|
321
|
+
reconnectNonce,
|
|
322
|
+
]);
|
|
313
323
|
|
|
314
324
|
const reconnect = () => {
|
|
315
325
|
setConnectTimedOut(false);
|
|
@@ -338,11 +348,15 @@ export function DesktopViewer({
|
|
|
338
348
|
case "viewer_cap":
|
|
339
349
|
overlay =
|
|
340
350
|
renderViewerCap?.() ??
|
|
341
|
-
defaultNotice(
|
|
351
|
+
defaultNotice(
|
|
352
|
+
"Too many viewers",
|
|
353
|
+
"This session has reached its live-viewer limit. Try again shortly.",
|
|
354
|
+
);
|
|
342
355
|
break;
|
|
343
356
|
case "unavailable":
|
|
344
357
|
overlay =
|
|
345
|
-
renderUnavailable?.(reason) ??
|
|
358
|
+
renderUnavailable?.(reason) ??
|
|
359
|
+
defaultNotice("Desktop unavailable", unavailableCopy(reason));
|
|
346
360
|
break;
|
|
347
361
|
case "consent":
|
|
348
362
|
overlay = renderConsentGate ? (
|
|
@@ -362,7 +376,11 @@ export function DesktopViewer({
|
|
|
362
376
|
);
|
|
363
377
|
break;
|
|
364
378
|
case "error":
|
|
365
|
-
overlay = defaultNotice(
|
|
379
|
+
overlay = defaultNotice(
|
|
380
|
+
"Desktop disconnected",
|
|
381
|
+
stream.error?.message ?? "The stream dropped.",
|
|
382
|
+
reconnect,
|
|
383
|
+
);
|
|
366
384
|
break;
|
|
367
385
|
case "connecting":
|
|
368
386
|
case "connected":
|
|
@@ -511,9 +529,7 @@ function TakeControlCallToAction({
|
|
|
511
529
|
<MousePointerClickIcon className="size-5" strokeWidth={2} />
|
|
512
530
|
</span>
|
|
513
531
|
<span className="flex flex-col items-start leading-tight">
|
|
514
|
-
<span className="text-og-base font-semibold text-og-fg">
|
|
515
|
-
Take control
|
|
516
|
-
</span>
|
|
532
|
+
<span className="text-og-base font-semibold text-og-fg">Take control</span>
|
|
517
533
|
<span className="text-og-xs text-og-fg-subtle">
|
|
518
534
|
{disabled && disabledReason ? disabledReason : "Drive the mouse & keyboard"}
|
|
519
535
|
</span>
|
|
@@ -594,10 +610,7 @@ function unavailableCopy(reason: CapabilityUnavailableReason | null): string {
|
|
|
594
610
|
function WarmingNotice({ onRetry }: { onRetry?: (() => void) | undefined }) {
|
|
595
611
|
return (
|
|
596
612
|
<div className="flex max-w-sm flex-col items-center gap-3 rounded-og-lg border border-og-border bg-og-bg/90 p-5 text-center text-og-base text-og-fg backdrop-blur-sm">
|
|
597
|
-
<LoaderCircleIcon
|
|
598
|
-
className="size-7 animate-og-spin text-og-accent"
|
|
599
|
-
strokeWidth={1.5}
|
|
600
|
-
/>
|
|
613
|
+
<LoaderCircleIcon className="size-7 animate-og-spin text-og-accent" strokeWidth={1.5} />
|
|
601
614
|
<div className="space-y-1">
|
|
602
615
|
<div className="font-medium">Warming the sandbox…</div>
|
|
603
616
|
<p className="text-og-sm text-og-fg-subtle">
|
|
@@ -622,8 +635,8 @@ function DefaultConsentGate({ shared, onAccept }: { shared: boolean; onAccept: (
|
|
|
622
635
|
<div className="max-w-sm rounded-og-lg border border-og-border bg-og-bg p-4 text-center text-og-base text-og-fg">
|
|
623
636
|
<div className="mb-1 font-medium">Watch the live desktop?</div>
|
|
624
637
|
<p className="mb-3 text-og-sm text-og-fg-subtle">
|
|
625
|
-
The desktop pixel stream is <strong>un-redacted</strong> — it can show secrets the agent
|
|
626
|
-
on screen.
|
|
638
|
+
The desktop pixel stream is <strong>un-redacted</strong> — it can show secrets the agent
|
|
639
|
+
prints on screen.
|
|
627
640
|
{shared
|
|
628
641
|
? " This sandbox is shared: you'll also see other sessions' agents on the same screen."
|
|
629
642
|
: ""}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GitFileDiff } from "@opengeni/sdk";
|
|
2
2
|
import { type ReactNode } from "react";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
|
+
import { PierreDiff } from "./pierre-diff";
|
|
4
5
|
|
|
5
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated Use {@link PierreDiff} directly. Retained only as a thin,
|
|
8
|
+
* back-compat alias that renders the Pierre (Shiki) renderer with a plain-text
|
|
9
|
+
* degrade. The old hand-rolled hunk renderer was removed in Workbench v2 — the
|
|
10
|
+
* workbench keeps exactly ONE diff renderer. This alias is removed next major.
|
|
11
|
+
*/
|
|
6
12
|
export type DiffTheme = {
|
|
7
13
|
addBackground?: string;
|
|
8
14
|
delBackground?: string;
|
|
@@ -10,54 +16,39 @@ export type DiffTheme = {
|
|
|
10
16
|
metaForeground?: string;
|
|
11
17
|
};
|
|
12
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated Use {@link PierreDiff}. `theme`/`onSelectFile`/`emptyState` from the
|
|
21
|
+
* removed hand-rolled renderer are accepted for source compatibility and ignored;
|
|
22
|
+
* `fallback` still overrides the plain-text degrade.
|
|
23
|
+
*/
|
|
13
24
|
export type DiffViewProps = {
|
|
14
|
-
/** From `useSandboxGit().diff` — the structured per-file hunks. */
|
|
15
25
|
diff: GitFileDiff[];
|
|
16
|
-
/** Rendered instead of the built-in diff (the Pierre-swap escape hatch). */
|
|
17
26
|
fallback?: ReactNode | undefined;
|
|
18
|
-
/** unified | split. Default "unified". */
|
|
19
27
|
layout?: "unified" | "split" | undefined;
|
|
28
|
+
themeType?: "dark" | "light" | undefined;
|
|
29
|
+
/** @deprecated line-background theming was part of the removed renderer. */
|
|
20
30
|
theme?: DiffTheme | undefined;
|
|
31
|
+
/** @deprecated the alias no longer renders a clickable file header. */
|
|
21
32
|
onSelectFile?: ((path: string) => void) | undefined;
|
|
22
|
-
/** Distinguishes "no changes" from "no repository mounted". */
|
|
23
33
|
isRepo?: boolean | undefined;
|
|
24
34
|
emptyState?: ReactNode | undefined;
|
|
25
35
|
className?: string | undefined;
|
|
26
36
|
};
|
|
27
37
|
|
|
28
|
-
const STATUS_LABEL: Record<GitFileDiff["status"], string> = {
|
|
29
|
-
added: "added",
|
|
30
|
-
modified: "modified",
|
|
31
|
-
deleted: "deleted",
|
|
32
|
-
renamed: "renamed",
|
|
33
|
-
copied: "copied",
|
|
34
|
-
untracked: "untracked",
|
|
35
|
-
ignored: "ignored",
|
|
36
|
-
conflicted: "conflicted",
|
|
37
|
-
typechange: "typechange",
|
|
38
|
-
};
|
|
39
|
-
|
|
40
38
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* consumer with Pierre's `@pierre/diffs` installed can swap it via `fallback`
|
|
45
|
-
* while keeping the same `GitFileDiff[]` data contract from the hook.
|
|
39
|
+
* @deprecated Use {@link PierreDiff} directly. Thin alias: renders `PierreDiff`
|
|
40
|
+
* (Shiki-highlighted, unified/split) with a plain-text fallback for a host
|
|
41
|
+
* without `@pierre/diffs`. Removed next major.
|
|
46
42
|
*/
|
|
47
43
|
export function DiffView({
|
|
48
44
|
diff,
|
|
49
45
|
fallback,
|
|
50
46
|
layout = "unified",
|
|
51
|
-
|
|
52
|
-
onSelectFile,
|
|
47
|
+
themeType,
|
|
53
48
|
isRepo = true,
|
|
54
49
|
emptyState,
|
|
55
50
|
className,
|
|
56
51
|
}: DiffViewProps) {
|
|
57
|
-
if (fallback !== undefined) {
|
|
58
|
-
return <div className={className}>{fallback}</div>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
52
|
if (diff.length === 0) {
|
|
62
53
|
return (
|
|
63
54
|
<div className={cn("p-3 text-og-sm text-og-fg-subtle", className)}>
|
|
@@ -65,166 +56,13 @@ export function DiffView({
|
|
|
65
56
|
</div>
|
|
66
57
|
);
|
|
67
58
|
}
|
|
68
|
-
|
|
69
|
-
return (
|
|
70
|
-
<div className={cn("min-w-0 overflow-auto", className)} data-opengeni-diff>
|
|
71
|
-
{diff.map((file) => (
|
|
72
|
-
<FileDiffBlock
|
|
73
|
-
key={file.path}
|
|
74
|
-
file={file}
|
|
75
|
-
layout={layout}
|
|
76
|
-
theme={theme}
|
|
77
|
-
{...(onSelectFile ? { onSelect: () => onSelectFile(file.path) } : {})}
|
|
78
|
-
/>
|
|
79
|
-
))}
|
|
80
|
-
</div>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function FileDiffBlock({
|
|
85
|
-
file,
|
|
86
|
-
layout,
|
|
87
|
-
theme,
|
|
88
|
-
onSelect,
|
|
89
|
-
}: {
|
|
90
|
-
file: GitFileDiff;
|
|
91
|
-
layout: "unified" | "split";
|
|
92
|
-
theme: DiffTheme | undefined;
|
|
93
|
-
onSelect?: (() => void) | undefined;
|
|
94
|
-
}) {
|
|
95
|
-
return (
|
|
96
|
-
<section className="mb-2 overflow-hidden rounded-og-sm border border-og-border">
|
|
97
|
-
<header
|
|
98
|
-
className={cn(
|
|
99
|
-
"flex items-center justify-between gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1 text-og-sm",
|
|
100
|
-
onSelect && "cursor-pointer",
|
|
101
|
-
)}
|
|
102
|
-
onClick={onSelect}
|
|
103
|
-
>
|
|
104
|
-
<span className="truncate font-og-mono">
|
|
105
|
-
{file.oldPath && file.oldPath !== file.path ? `${file.oldPath} → ${file.path}` : file.path}
|
|
106
|
-
</span>
|
|
107
|
-
<span className="flex shrink-0 items-center gap-2">
|
|
108
|
-
<span className="rounded-og-xs bg-og-bg px-1 py-0.5 text-og-xs text-og-fg-subtle">
|
|
109
|
-
{STATUS_LABEL[file.status]}
|
|
110
|
-
</span>
|
|
111
|
-
<span className="text-og-status-idle">+{file.additions}</span>
|
|
112
|
-
<span className="text-og-status-failed">−{file.deletions}</span>
|
|
113
|
-
</span>
|
|
114
|
-
</header>
|
|
115
|
-
{file.isBinary ? (
|
|
116
|
-
<div className="px-2 py-3 text-og-sm text-og-fg-subtle">Binary file not shown</div>
|
|
117
|
-
) : file.truncated ? (
|
|
118
|
-
<div className="px-2 py-3 text-og-sm text-og-fg-subtle">Diff too large — truncated</div>
|
|
119
|
-
) : layout === "split" ? (
|
|
120
|
-
<SplitHunks file={file} theme={theme} />
|
|
121
|
-
) : (
|
|
122
|
-
<UnifiedHunks file={file} theme={theme} />
|
|
123
|
-
)}
|
|
124
|
-
</section>
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function lineBg(type: GitDiffLine["type"], theme: DiffTheme | undefined): string | undefined {
|
|
129
|
-
if (type === "add") return theme?.addBackground ?? "var(--og-color-diff-add-bg)";
|
|
130
|
-
if (type === "del") return theme?.delBackground ?? "var(--og-color-diff-del-bg)";
|
|
131
|
-
if (type === "meta") return undefined;
|
|
132
|
-
return theme?.contextBackground;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function marker(type: GitDiffLine["type"]): string {
|
|
136
|
-
if (type === "add") return "+";
|
|
137
|
-
if (type === "del") return "−";
|
|
138
|
-
return " ";
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function UnifiedHunks({ file, theme }: { file: GitFileDiff; theme: DiffTheme | undefined }) {
|
|
142
|
-
return (
|
|
143
|
-
<div className="font-og-mono text-og-xs">
|
|
144
|
-
{file.hunks.map((hunk, hi) => (
|
|
145
|
-
<div key={`${file.path}-h${hi}`}>
|
|
146
|
-
<div
|
|
147
|
-
className="px-2 py-0.5 text-og-accent"
|
|
148
|
-
style={{ color: theme?.metaForeground }}
|
|
149
|
-
>
|
|
150
|
-
{hunk.header}
|
|
151
|
-
</div>
|
|
152
|
-
{hunk.lines.map((line, li) => (
|
|
153
|
-
<div
|
|
154
|
-
key={`${file.path}-h${hi}-l${li}`}
|
|
155
|
-
className="flex"
|
|
156
|
-
style={{ backgroundColor: lineBg(line.type, theme) }}
|
|
157
|
-
>
|
|
158
|
-
<span className="w-10 shrink-0 select-none px-1 text-right text-og-fg-subtle">
|
|
159
|
-
{line.oldNo ?? ""}
|
|
160
|
-
</span>
|
|
161
|
-
<span className="w-10 shrink-0 select-none px-1 text-right text-og-fg-subtle">
|
|
162
|
-
{line.newNo ?? ""}
|
|
163
|
-
</span>
|
|
164
|
-
<span className="w-4 shrink-0 select-none text-center text-og-fg-subtle">
|
|
165
|
-
{marker(line.type)}
|
|
166
|
-
</span>
|
|
167
|
-
<span className="whitespace-pre-wrap break-all px-1">{line.text}</span>
|
|
168
|
-
</div>
|
|
169
|
-
))}
|
|
170
|
-
</div>
|
|
171
|
-
))}
|
|
172
|
-
</div>
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function SplitHunks({ file, theme }: { file: GitFileDiff; theme: DiffTheme | undefined }) {
|
|
177
59
|
return (
|
|
178
|
-
<
|
|
179
|
-
{
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
left.push(line);
|
|
186
|
-
right.push(line);
|
|
187
|
-
} else if (line.type === "del") {
|
|
188
|
-
left.push(line);
|
|
189
|
-
} else {
|
|
190
|
-
right.push(line);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
const rows = Math.max(left.length, right.length);
|
|
194
|
-
return (
|
|
195
|
-
<div key={`${file.path}-h${hi}`}>
|
|
196
|
-
<div className="px-2 py-0.5 text-og-accent" style={{ color: theme?.metaForeground }}>
|
|
197
|
-
{hunk.header}
|
|
198
|
-
</div>
|
|
199
|
-
{Array.from({ length: rows }).map((_, ri) => {
|
|
200
|
-
const l = left[ri] ?? null;
|
|
201
|
-
const r = right[ri] ?? null;
|
|
202
|
-
return (
|
|
203
|
-
<div key={`${file.path}-h${hi}-r${ri}`} className="flex">
|
|
204
|
-
<span
|
|
205
|
-
className="flex w-1/2 min-w-0"
|
|
206
|
-
style={{ backgroundColor: l ? lineBg(l.type, theme) : undefined }}
|
|
207
|
-
>
|
|
208
|
-
<span className="w-10 shrink-0 select-none px-1 text-right text-og-fg-subtle">
|
|
209
|
-
{l?.oldNo ?? ""}
|
|
210
|
-
</span>
|
|
211
|
-
<span className="whitespace-pre-wrap break-all px-1">{l?.text ?? ""}</span>
|
|
212
|
-
</span>
|
|
213
|
-
<span
|
|
214
|
-
className="flex w-1/2 min-w-0 border-l border-og-border"
|
|
215
|
-
style={{ backgroundColor: r ? lineBg(r.type, theme) : undefined }}
|
|
216
|
-
>
|
|
217
|
-
<span className="w-10 shrink-0 select-none px-1 text-right text-og-fg-subtle">
|
|
218
|
-
{r?.newNo ?? ""}
|
|
219
|
-
</span>
|
|
220
|
-
<span className="whitespace-pre-wrap break-all px-1">{r?.text ?? ""}</span>
|
|
221
|
-
</span>
|
|
222
|
-
</div>
|
|
223
|
-
);
|
|
224
|
-
})}
|
|
225
|
-
</div>
|
|
226
|
-
);
|
|
227
|
-
})}
|
|
228
|
-
</div>
|
|
60
|
+
<PierreDiff
|
|
61
|
+
diff={diff}
|
|
62
|
+
layout={layout}
|
|
63
|
+
themeType={themeType}
|
|
64
|
+
{...(fallback !== undefined ? { fallback } : {})}
|
|
65
|
+
className={className}
|
|
66
|
+
/>
|
|
229
67
|
);
|
|
230
68
|
}
|
|
@@ -95,7 +95,10 @@ export function EnrollmentConsent({
|
|
|
95
95
|
className={className}
|
|
96
96
|
tone="danger"
|
|
97
97
|
title="Could not complete enrollment"
|
|
98
|
-
body={
|
|
98
|
+
body={
|
|
99
|
+
errorMessage ??
|
|
100
|
+
"The code may have expired. Re-run the install one-liner on the machine for a fresh code."
|
|
101
|
+
}
|
|
99
102
|
/>
|
|
100
103
|
);
|
|
101
104
|
}
|
|
@@ -113,10 +116,13 @@ export function EnrollmentConsent({
|
|
|
113
116
|
<ShieldAlertIcon className="size-5" aria-hidden />
|
|
114
117
|
</span>
|
|
115
118
|
<div className="min-w-0">
|
|
116
|
-
<h1 className="text-og-md font-semibold text-og-fg">
|
|
119
|
+
<h1 className="text-og-md font-semibold text-og-fg">
|
|
120
|
+
Give the agent your whole machine?
|
|
121
|
+
</h1>
|
|
117
122
|
<p className="mt-1 text-og-base text-og-fg-muted">
|
|
118
|
-
Approving lets the OpenGeni agent run on
|
|
119
|
-
|
|
123
|
+
Approving lets the OpenGeni agent run on{" "}
|
|
124
|
+
<span className="font-medium text-og-fg">{machine.machineName}</span> with full access.
|
|
125
|
+
This is your real computer — not a sandbox.
|
|
120
126
|
</p>
|
|
121
127
|
</div>
|
|
122
128
|
</div>
|
|
@@ -127,7 +133,9 @@ export function EnrollmentConsent({
|
|
|
127
133
|
<span className="font-og-mono text-og-fg-subtle">
|
|
128
134
|
{machine.os}/{machine.arch}
|
|
129
135
|
</span>
|
|
130
|
-
<span aria-hidden className="text-og-fg-subtle"
|
|
136
|
+
<span aria-hidden className="text-og-fg-subtle">
|
|
137
|
+
·
|
|
138
|
+
</span>
|
|
131
139
|
<span className="text-og-fg-subtle">
|
|
132
140
|
code <span className="font-og-mono font-medium text-og-fg">{userCode}</span>
|
|
133
141
|
</span>
|
|
@@ -171,15 +179,16 @@ export function EnrollmentConsent({
|
|
|
171
179
|
Also let the agent control my mouse & keyboard
|
|
172
180
|
</span>
|
|
173
181
|
<span className="mt-0.5 block text-og-sm text-og-fg-muted">
|
|
174
|
-
Optional. Enables computer-use — the agent can move the pointer, type, and click on
|
|
175
|
-
off to let it only watch.
|
|
182
|
+
Optional. Enables computer-use — the agent can move the pointer, type, and click on
|
|
183
|
+
this machine's desktop. Leave off to let it only watch.
|
|
176
184
|
</span>
|
|
177
185
|
</span>
|
|
178
186
|
</label>
|
|
179
187
|
) : (
|
|
180
188
|
<p className="flex items-start gap-2 rounded-og-md border border-og-border bg-og-bg px-3 py-2 text-og-sm text-og-fg-muted">
|
|
181
189
|
<CircleAlertIcon className="mt-px size-3.5 shrink-0 text-og-fg-subtle" aria-hidden />
|
|
182
|
-
This machine has no display, so screen control isn't available — files, terminal, and git
|
|
190
|
+
This machine has no display, so screen control isn't available — files, terminal, and git
|
|
191
|
+
only.
|
|
183
192
|
</p>
|
|
184
193
|
)}
|
|
185
194
|
|
|
@@ -225,7 +234,11 @@ function ConsentResult({
|
|
|
225
234
|
? "border-og-status-failed/30"
|
|
226
235
|
: "border-og-border";
|
|
227
236
|
const iconClass =
|
|
228
|
-
tone === "ok"
|
|
237
|
+
tone === "ok"
|
|
238
|
+
? "text-og-status-running"
|
|
239
|
+
: tone === "danger"
|
|
240
|
+
? "text-og-status-failed"
|
|
241
|
+
: "text-og-fg-subtle";
|
|
229
242
|
return (
|
|
230
243
|
<div
|
|
231
244
|
data-enrollment-result={tone}
|
|
@@ -235,7 +248,12 @@ function ConsentResult({
|
|
|
235
248
|
className,
|
|
236
249
|
)}
|
|
237
250
|
>
|
|
238
|
-
<span
|
|
251
|
+
<span
|
|
252
|
+
className={cn(
|
|
253
|
+
"flex size-10 items-center justify-center rounded-full bg-og-surface-2",
|
|
254
|
+
iconClass,
|
|
255
|
+
)}
|
|
256
|
+
>
|
|
239
257
|
<LaptopIcon className="size-5" aria-hidden />
|
|
240
258
|
</span>
|
|
241
259
|
<h1 className="text-og-md font-semibold text-og-fg">{title}</h1>
|
|
@@ -99,7 +99,9 @@ export function EnrollmentDeviceFlow({
|
|
|
99
99
|
|
|
100
100
|
{installCommand ? (
|
|
101
101
|
<div className="flex flex-col gap-1.5">
|
|
102
|
-
<span className="text-[11px] font-medium uppercase tracking-wide text-og-fg-subtle">
|
|
102
|
+
<span className="text-[11px] font-medium uppercase tracking-wide text-og-fg-subtle">
|
|
103
|
+
1 · Run on the machine
|
|
104
|
+
</span>
|
|
103
105
|
<div className="flex items-center justify-between gap-2 overflow-hidden rounded-og-md border border-og-border bg-og-bg px-2.5 py-1.5">
|
|
104
106
|
<code className="flex min-w-0 items-center gap-2 overflow-x-auto font-og-mono text-[12px] text-og-fg">
|
|
105
107
|
<TerminalIcon className="size-3.5 shrink-0 text-og-fg-subtle" aria-hidden />
|
|
@@ -164,13 +166,14 @@ export function EnrollmentDeviceFlow({
|
|
|
164
166
|
{awaitingCode ? (
|
|
165
167
|
<>
|
|
166
168
|
The one-liner prints a short code. Open{" "}
|
|
167
|
-
<span className="font-og-mono text-og-fg-muted">{verificationUri}</span> and confirm the
|
|
168
|
-
matches.
|
|
169
|
+
<span className="font-og-mono text-og-fg-muted">{verificationUri}</span> and confirm the
|
|
170
|
+
code shown there matches.
|
|
169
171
|
</>
|
|
170
172
|
) : (
|
|
171
173
|
<>
|
|
172
|
-
Confirm code <span className="font-og-mono text-og-fg-muted">{userCode}</span> — the one
|
|
173
|
-
one-liner — at
|
|
174
|
+
Confirm code <span className="font-og-mono text-og-fg-muted">{userCode}</span> — the one
|
|
175
|
+
printed by the one-liner — at{" "}
|
|
176
|
+
<span className="font-og-mono text-og-fg-muted">{verificationUri}</span>.
|
|
174
177
|
{typeof expiresInSeconds === "number" ? (
|
|
175
178
|
<> Expires in {Math.max(0, Math.round(expiresInSeconds / 60))} min.</>
|
|
176
179
|
) : null}
|