@opengeni/react 0.3.0 → 0.4.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/dist/index.d.ts +1035 -14
- package/dist/index.js +6867 -1884
- package/dist/index.js.map +1 -1
- package/package.json +65 -2
- package/src/client.ts +21 -0
- package/src/components/code-editor.tsx +398 -0
- package/src/components/desktop-viewer.tsx +647 -0
- package/src/components/diff-view.tsx +230 -0
- package/src/components/file-browser.tsx +838 -0
- package/src/components/message-timeline.tsx +70 -196
- package/src/components/pierre-diff.tsx +140 -0
- package/src/components/pierre-file.tsx +142 -0
- package/src/components/sandbox-files.tsx +509 -0
- package/src/components/sandbox-terminal.tsx +425 -0
- package/src/components/workspace-dock.tsx +247 -0
- package/src/hooks/use-desktop-stream.ts +214 -0
- package/src/hooks/use-sandbox-files.ts +670 -0
- package/src/hooks/use-sandbox-git.ts +105 -0
- package/src/hooks/use-sandbox-terminal.ts +226 -0
- package/src/hooks/use-session-capabilities.ts +415 -0
- package/src/hooks/use-terminal-stream.ts +207 -0
- package/src/index.ts +111 -2
- package/src/lib/cn.ts +20 -1
- package/src/lib/git-patch.ts +37 -0
- package/src/lib/use-theme-type.ts +40 -0
- package/src/lib/xterm-theme.ts +34 -0
- package/src/timeline/activity-rail.tsx +207 -0
- package/src/timeline/disclosure-context.tsx +34 -0
- package/src/timeline/index.ts +85 -0
- package/src/timeline/parsers.ts +248 -0
- package/src/{timeline.ts → timeline/projection.ts} +59 -134
- package/src/timeline/registry.ts +96 -0
- package/src/timeline/screenshot-lightbox.tsx +152 -0
- package/src/timeline/shared.tsx +481 -0
- package/src/timeline/tool-diff.tsx +91 -0
- package/src/timeline/tool-renderers.tsx +882 -0
- package/src/timeline/turn-summary.tsx +125 -0
- package/src/timeline/types.ts +131 -0
- package/src/types/external.d.ts +7 -0
- package/styles/index.css +72 -0
package/src/index.ts
CHANGED
|
@@ -46,6 +46,37 @@ export type { UseBillingUsageOptions, UseBillingUsageResult } from "./hooks/use-
|
|
|
46
46
|
export { useAvailableModels } from "./hooks/use-available-models";
|
|
47
47
|
export type { UseAvailableModelsOptions, UseAvailableModelsResult } from "./hooks/use-available-models";
|
|
48
48
|
|
|
49
|
+
// Sandbox surfacing (Phase 5): capability negotiation + terminal/files/diff/desktop
|
|
50
|
+
export { useSessionCapabilities } from "./hooks/use-session-capabilities";
|
|
51
|
+
export type {
|
|
52
|
+
SessionCapabilitiesState,
|
|
53
|
+
UseSessionCapabilitiesOptions,
|
|
54
|
+
UseSessionCapabilitiesResult,
|
|
55
|
+
} from "./hooks/use-session-capabilities";
|
|
56
|
+
export { useDesktopStream } from "./hooks/use-desktop-stream";
|
|
57
|
+
export type { UseDesktopStreamOptions, UseDesktopStreamResult } from "./hooks/use-desktop-stream";
|
|
58
|
+
export { useTerminalStream } from "./hooks/use-terminal-stream";
|
|
59
|
+
export type {
|
|
60
|
+
TerminalStreamStatus,
|
|
61
|
+
UseTerminalStreamOptions,
|
|
62
|
+
UseTerminalStreamResult,
|
|
63
|
+
} from "./hooks/use-terminal-stream";
|
|
64
|
+
export { useSandboxTerminal } from "./hooks/use-sandbox-terminal";
|
|
65
|
+
export type {
|
|
66
|
+
TerminalChunk,
|
|
67
|
+
UseSandboxTerminalOptions,
|
|
68
|
+
UseSandboxTerminalResult,
|
|
69
|
+
} from "./hooks/use-sandbox-terminal";
|
|
70
|
+
export { useSandboxFiles } from "./hooks/use-sandbox-files";
|
|
71
|
+
export type {
|
|
72
|
+
FileTreeNode,
|
|
73
|
+
FileTreeStatus,
|
|
74
|
+
UseSandboxFilesOptions,
|
|
75
|
+
UseSandboxFilesResult,
|
|
76
|
+
} from "./hooks/use-sandbox-files";
|
|
77
|
+
export { useSandboxGit } from "./hooks/use-sandbox-git";
|
|
78
|
+
export type { UseSandboxGitOptions, UseSandboxGitResult } from "./hooks/use-sandbox-git";
|
|
79
|
+
|
|
49
80
|
// Pending-approvals projection
|
|
50
81
|
export { approvalsFromRequiresAction, projectPendingApprovals } from "./approvals";
|
|
51
82
|
export type { PendingApproval } from "./approvals";
|
|
@@ -53,13 +84,13 @@ export type { PendingApproval } from "./approvals";
|
|
|
53
84
|
// Timeline projection
|
|
54
85
|
export {
|
|
55
86
|
buildTimeline,
|
|
56
|
-
compactPayloadPreview,
|
|
57
87
|
extractSessionRef,
|
|
58
88
|
groupTimeline,
|
|
59
89
|
sessionStatusFromEvents,
|
|
60
90
|
toolDisplayName,
|
|
61
91
|
} from "./timeline";
|
|
62
92
|
export type {
|
|
93
|
+
ActivityItem,
|
|
63
94
|
AgentMessageItem,
|
|
64
95
|
GoalItem,
|
|
65
96
|
NoticeItem,
|
|
@@ -73,6 +104,60 @@ export type {
|
|
|
73
104
|
WorkerItem,
|
|
74
105
|
} from "./timeline";
|
|
75
106
|
|
|
107
|
+
// Tool-renderer registry + the per-tool renderers (the timeline's extension API)
|
|
108
|
+
export { createDefaultToolRegistry, createToolRegistry, defaultToolRegistry, rawTypeOf } from "./timeline";
|
|
109
|
+
export type {
|
|
110
|
+
CreateToolRegistryOptions,
|
|
111
|
+
ToolRegistry,
|
|
112
|
+
ToolRegistryEntry,
|
|
113
|
+
ToolRenderer,
|
|
114
|
+
ToolRendererProps,
|
|
115
|
+
} from "./timeline";
|
|
116
|
+
|
|
117
|
+
// Timeline rendering primitives + the screenshot lightbox (compose custom renderers)
|
|
118
|
+
export {
|
|
119
|
+
ActivityDisclosure,
|
|
120
|
+
ActivityRail,
|
|
121
|
+
BodyNote,
|
|
122
|
+
DisclosureDefaultsProvider,
|
|
123
|
+
LightboxProvider,
|
|
124
|
+
MediaEmpty,
|
|
125
|
+
MediaSkeleton,
|
|
126
|
+
PayloadBlock,
|
|
127
|
+
ScreenshotFigure,
|
|
128
|
+
TermBlock,
|
|
129
|
+
Thumbnail,
|
|
130
|
+
TurnSummary,
|
|
131
|
+
useLightbox,
|
|
132
|
+
useLightboxOptional,
|
|
133
|
+
} from "./timeline";
|
|
134
|
+
export type {
|
|
135
|
+
ActivityDisclosureProps,
|
|
136
|
+
ActivityRailProps,
|
|
137
|
+
DisclosureChip,
|
|
138
|
+
TurnOutcome,
|
|
139
|
+
TurnSummaryProps,
|
|
140
|
+
} from "./timeline";
|
|
141
|
+
|
|
142
|
+
// Pure provider-shape parsers (exec banner, V4A diff, secret redaction, …)
|
|
143
|
+
export {
|
|
144
|
+
applyPatchOps,
|
|
145
|
+
controlCaret,
|
|
146
|
+
execTruncated,
|
|
147
|
+
isApplyPatch,
|
|
148
|
+
isExecSessionLostBanner,
|
|
149
|
+
looksBinary,
|
|
150
|
+
parseExecBannerSessionId,
|
|
151
|
+
parseToolArgs,
|
|
152
|
+
redactSecrets,
|
|
153
|
+
sandboxCommandExitCode,
|
|
154
|
+
stripExecBanner,
|
|
155
|
+
tailPeek,
|
|
156
|
+
unwrapMcpOutput,
|
|
157
|
+
v4aToGitFileDiff,
|
|
158
|
+
} from "./timeline";
|
|
159
|
+
export type { ApplyPatchOperation } from "./timeline";
|
|
160
|
+
|
|
76
161
|
// Slash-command palette (registry + UI + hook)
|
|
77
162
|
export {
|
|
78
163
|
argHint,
|
|
@@ -107,7 +192,7 @@ export { ChatComposer } from "./components/chat-composer";
|
|
|
107
192
|
export type { ChatComposerProps } from "./components/chat-composer";
|
|
108
193
|
export { ModelPicker } from "./components/model-picker";
|
|
109
194
|
export type { ModelPickerProps } from "./components/model-picker";
|
|
110
|
-
export { MessageTimeline } from "./components/message-timeline";
|
|
195
|
+
export { MessageTimeline, TimelineRow } from "./components/message-timeline";
|
|
111
196
|
export type { MessageTimelineProps } from "./components/message-timeline";
|
|
112
197
|
export { Markdown } from "./components/markdown";
|
|
113
198
|
export type { MarkdownProps } from "./components/markdown";
|
|
@@ -116,6 +201,30 @@ export type { SessionStatusProps, StatusDotProps, SessionStatusMeta } from "./co
|
|
|
116
201
|
export { FleetTile, sessionDisplayTitle } from "./components/fleet-tile";
|
|
117
202
|
export type { FleetTileProps } from "./components/fleet-tile";
|
|
118
203
|
|
|
204
|
+
// Sandbox surfacing components (Phase 5)
|
|
205
|
+
export { SandboxTerminal } from "./components/sandbox-terminal";
|
|
206
|
+
export type { SandboxTerminalProps, XtermTheme } from "./components/sandbox-terminal";
|
|
207
|
+
export { FileBrowser } from "./components/file-browser";
|
|
208
|
+
export type { FileBrowserProps } from "./components/file-browser";
|
|
209
|
+
export { DiffView } from "./components/diff-view";
|
|
210
|
+
export type { DiffViewProps, DiffTheme } from "./components/diff-view";
|
|
211
|
+
export { PierreDiff } from "./components/pierre-diff";
|
|
212
|
+
export type { PierreDiffProps } from "./components/pierre-diff";
|
|
213
|
+
export { PierreFile } from "./components/pierre-file";
|
|
214
|
+
export type { PierreFileProps } from "./components/pierre-file";
|
|
215
|
+
export { CodeEditor, languageForPath } from "./components/code-editor";
|
|
216
|
+
export type { CodeEditorProps } from "./components/code-editor";
|
|
217
|
+
export { SandboxFiles } from "./components/sandbox-files";
|
|
218
|
+
export type { SandboxFilesProps } from "./components/sandbox-files";
|
|
219
|
+
export { DesktopViewer } from "./components/desktop-viewer";
|
|
220
|
+
export type { DesktopViewerProps } from "./components/desktop-viewer";
|
|
221
|
+
export { WorkspaceDock } from "./components/workspace-dock";
|
|
222
|
+
export type { WorkspaceDockProps, WorkspaceTab } from "./components/workspace-dock";
|
|
223
|
+
|
|
224
|
+
// Sandbox helpers
|
|
225
|
+
export { gitFileDiffToPatch } from "./lib/git-patch";
|
|
226
|
+
export { xtermThemeFromTokens } from "./lib/xterm-theme";
|
|
227
|
+
|
|
119
228
|
// Utilities
|
|
120
229
|
export { cn } from "./lib/cn";
|
|
121
230
|
export { formatBytes, formatRelativeTime, stringifyPayload, truncate, tryParseJson } from "./lib/format";
|
package/src/lib/cn.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { clsx, type ClassValue } from "clsx";
|
|
2
|
-
import {
|
|
2
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
/* ----------------------------------------------------------------------------
|
|
5
|
+
tailwind-merge, taught our design tokens.
|
|
6
|
+
|
|
7
|
+
Our type scale and color ramp share the `og-` prefix: `text-og-base` is a
|
|
8
|
+
FONT SIZE, `text-og-fg-muted` is a COLOR. Stock tailwind-merge can't tell
|
|
9
|
+
them apart — it lumps every `text-og-*` into one `font-size` group and, when
|
|
10
|
+
two land in the same class list, drops the earlier one. That silently ate the
|
|
11
|
+
size off rows like `text-og-base text-og-fg-muted`, leaving titles at the
|
|
12
|
+
browser-default 16px. Register the custom font-size scale so the size and the
|
|
13
|
+
color live in different conflict groups and both survive a merge.
|
|
14
|
+
-------------------------------------------------------------------------- */
|
|
15
|
+
const twMerge = extendTailwindMerge({
|
|
16
|
+
extend: {
|
|
17
|
+
classGroups: {
|
|
18
|
+
"font-size": [{ text: ["og-xs", "og-sm", "og-base", "og-md"] }],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
3
22
|
|
|
4
23
|
/** Merge class names with Tailwind-aware conflict resolution. */
|
|
5
24
|
export function cn(...inputs: ClassValue[]): string {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { GitFileDiff } from "@opengeni/sdk";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reconstruct a unified-diff patch string for a single `GitFileDiff` so it can
|
|
5
|
+
* be fed to a generic patch renderer (e.g. Pierre's `PatchDiff`). The hook
|
|
6
|
+
* already carries per-line old/new numbers and a hunk header, so we emit a
|
|
7
|
+
* conventional `--- / +++ / @@` patch the parser understands.
|
|
8
|
+
*/
|
|
9
|
+
export function gitFileDiffToPatch(file: GitFileDiff): string {
|
|
10
|
+
const oldPath = file.oldPath ?? file.path;
|
|
11
|
+
const newPath = file.path;
|
|
12
|
+
const lines: string[] = [];
|
|
13
|
+
lines.push(`diff --git a/${oldPath} b/${newPath}`);
|
|
14
|
+
if (file.status === "deleted") {
|
|
15
|
+
lines.push(`--- a/${oldPath}`);
|
|
16
|
+
lines.push(`+++ /dev/null`);
|
|
17
|
+
} else if (file.status === "added" || file.status === "untracked") {
|
|
18
|
+
lines.push(`--- /dev/null`);
|
|
19
|
+
lines.push(`+++ b/${newPath}`);
|
|
20
|
+
} else {
|
|
21
|
+
lines.push(`--- a/${oldPath}`);
|
|
22
|
+
lines.push(`+++ b/${newPath}`);
|
|
23
|
+
}
|
|
24
|
+
for (const hunk of file.hunks) {
|
|
25
|
+
const header =
|
|
26
|
+
hunk.header && hunk.header.startsWith("@@")
|
|
27
|
+
? hunk.header
|
|
28
|
+
: `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`;
|
|
29
|
+
lines.push(header);
|
|
30
|
+
for (const line of hunk.lines) {
|
|
31
|
+
if (line.type === "meta") continue;
|
|
32
|
+
const prefix = line.type === "add" ? "+" : line.type === "del" ? "-" : " ";
|
|
33
|
+
lines.push(`${prefix}${line.text}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return lines.join("\n") + "\n";
|
|
37
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
/* ----------------------------------------------------------------------------
|
|
4
|
+
useThemeType
|
|
5
|
+
|
|
6
|
+
Resolve the effective dark/light theme for surfaces (the Pierre/Shiki diff)
|
|
7
|
+
that render outside the reach of host CSS — they need the theme as a value,
|
|
8
|
+
not a cascade. An explicit prop always wins; otherwise read the host's
|
|
9
|
+
`data-og-theme` attribute (set on `<html>` or any ancestor by the same opt-in
|
|
10
|
+
the tokens use) and default to dark, the first-class theme. A MutationObserver
|
|
11
|
+
keeps it live across runtime theme flips.
|
|
12
|
+
|
|
13
|
+
One detector, shared by every diff surface (the Files tab and the timeline),
|
|
14
|
+
so the two can never drift onto different themes.
|
|
15
|
+
-------------------------------------------------------------------------- */
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the diff theme. An explicit `forced` value wins; otherwise auto-detect
|
|
19
|
+
* from the host `data-og-theme` (defaulting to dark) and track live flips.
|
|
20
|
+
*/
|
|
21
|
+
export function useThemeType(forced: "dark" | "light" | undefined): "dark" | "light" {
|
|
22
|
+
const [detected, setDetected] = useState<"dark" | "light">("dark");
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (forced || typeof document === "undefined") return;
|
|
25
|
+
const read = () => {
|
|
26
|
+
const el = document.querySelector("[data-og-theme]");
|
|
27
|
+
const value = el?.getAttribute("data-og-theme");
|
|
28
|
+
setDetected(value === "light" ? "light" : "dark");
|
|
29
|
+
};
|
|
30
|
+
read();
|
|
31
|
+
const observer = new MutationObserver(read);
|
|
32
|
+
observer.observe(document.documentElement, {
|
|
33
|
+
attributes: true,
|
|
34
|
+
attributeFilter: ["data-og-theme"],
|
|
35
|
+
subtree: true,
|
|
36
|
+
});
|
|
37
|
+
return () => observer.disconnect();
|
|
38
|
+
}, [forced]);
|
|
39
|
+
return forced ?? detected;
|
|
40
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { XtermTheme } from "../components/sandbox-terminal";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Derive an xterm `ITheme` subset from the live OKLCH `--og-*` token system (or
|
|
5
|
+
* the app's `--color-*` aliases). Reads the COMPUTED values so xterm — which
|
|
6
|
+
* paints into a canvas and can't consume CSS vars — gets concrete colors. Call
|
|
7
|
+
* on mount and re-derive on a `data-og-theme` flip.
|
|
8
|
+
*
|
|
9
|
+
* SSR-safe: returns undefined off the DOM (the caller keeps xterm's defaults).
|
|
10
|
+
*/
|
|
11
|
+
export function xtermThemeFromTokens(root?: HTMLElement | null): XtermTheme | undefined {
|
|
12
|
+
if (typeof window === "undefined" || typeof getComputedStyle === "undefined") return undefined;
|
|
13
|
+
const el = root ?? document.documentElement;
|
|
14
|
+
const style = getComputedStyle(el);
|
|
15
|
+
const read = (names: string[]): string | undefined => {
|
|
16
|
+
for (const name of names) {
|
|
17
|
+
const value = style.getPropertyValue(name).trim();
|
|
18
|
+
if (value) return value;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
};
|
|
22
|
+
const bg = read(["--og-color-bg", "--color-bg"]);
|
|
23
|
+
const fg = read(["--og-color-fg", "--color-fg"]);
|
|
24
|
+
const accent = read(["--og-color-accent", "--color-brand", "--color-accent"]);
|
|
25
|
+
const theme: XtermTheme = {};
|
|
26
|
+
if (bg) theme.background = bg;
|
|
27
|
+
if (fg) theme.foreground = fg;
|
|
28
|
+
if (accent) {
|
|
29
|
+
theme.cursor = accent;
|
|
30
|
+
theme.selectionBackground = accent;
|
|
31
|
+
}
|
|
32
|
+
if (bg) theme.cursorAccent = bg;
|
|
33
|
+
return Object.keys(theme).length > 0 ? theme : undefined;
|
|
34
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { BotIcon, BrainIcon, SquareTerminalIcon } from "lucide-react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
import { truncate } from "../lib/format";
|
|
4
|
+
import { defaultToolRegistry } from "./tool-renderers";
|
|
5
|
+
import type { ToolRegistry } from "./registry";
|
|
6
|
+
import { PayloadBlock, ActivityDisclosure } from "./shared";
|
|
7
|
+
import { toolDisplayName } from "./projection";
|
|
8
|
+
import type { ActivityItem, ReasoningItem, SandboxItem, WorkerItem } from "./types";
|
|
9
|
+
|
|
10
|
+
/* ----------------------------------------------------------------------------
|
|
11
|
+
Activity rail
|
|
12
|
+
|
|
13
|
+
Renders a run of clustered activity items (reasoning, tool calls, workers,
|
|
14
|
+
sandbox ops) as the left-bordered column between chat messages. Tool calls
|
|
15
|
+
resolve through the renderer registry; everything else has a first-class row.
|
|
16
|
+
|
|
17
|
+
Shared by `MessageTimeline` and the component demo so both draw the exact
|
|
18
|
+
same rail — no divergence.
|
|
19
|
+
-------------------------------------------------------------------------- */
|
|
20
|
+
|
|
21
|
+
export type ActivityRailProps = {
|
|
22
|
+
items: ActivityItem[];
|
|
23
|
+
/** Renderer registry for tool calls. Defaults to {@link defaultToolRegistry}. */
|
|
24
|
+
toolRegistry?: ToolRegistry | undefined;
|
|
25
|
+
/** Drill into a spawned worker session. */
|
|
26
|
+
onOpenSession?: ((sessionId: string) => void) | undefined;
|
|
27
|
+
/** Drop the left rule + indent (used inside a folded turn summary). */
|
|
28
|
+
bare?: boolean | undefined;
|
|
29
|
+
className?: string | undefined;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The "family" a row belongs to, for light intra-rail grouping. Consecutive
|
|
34
|
+
* rows of the same family sit tight; a family change gets a little extra top
|
|
35
|
+
* margin so a long run reads as clusters rather than one undifferentiated wall.
|
|
36
|
+
*/
|
|
37
|
+
function familyOf(item: ActivityItem): string {
|
|
38
|
+
if (item.kind === "tool-call") {
|
|
39
|
+
return item.name === "exec_command" || item.name === "write_stdin" ? "terminal" : item.name;
|
|
40
|
+
}
|
|
41
|
+
return item.kind;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function ActivityRail({ items, toolRegistry = defaultToolRegistry, onOpenSession, bare, className }: ActivityRailProps) {
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
className={cn(
|
|
48
|
+
// Rows sit TIGHT by default (gap-0.5) so a same-family run reads as one
|
|
49
|
+
// calm cluster; a family change opens real breathing room (mt-3) below,
|
|
50
|
+
// so a long rail reads as a few clusters, not a metronome of rows.
|
|
51
|
+
"flex flex-col gap-0.5",
|
|
52
|
+
!bare && "animate-og-enter border-l-2 border-og-border pl-3 sm:pl-4",
|
|
53
|
+
className,
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
{items.map((item, index) => {
|
|
57
|
+
const newFamily = index > 0 && familyOf(item) !== familyOf(items[index - 1]!);
|
|
58
|
+
const row = renderActivity(item, toolRegistry, onOpenSession);
|
|
59
|
+
return (
|
|
60
|
+
<div key={item.id} className={cn(newFamily && "mt-3")}>
|
|
61
|
+
{row}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
})}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A never-reachable guard: adding an `ActivityItem` kind is now a compile error. */
|
|
70
|
+
function assertNever(item: never): never {
|
|
71
|
+
throw new Error(`ActivityRail: unhandled activity item ${JSON.stringify(item)}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function renderActivity(
|
|
75
|
+
item: ActivityItem,
|
|
76
|
+
toolRegistry: ToolRegistry,
|
|
77
|
+
onOpenSession: ((sessionId: string) => void) | undefined,
|
|
78
|
+
) {
|
|
79
|
+
switch (item.kind) {
|
|
80
|
+
case "reasoning":
|
|
81
|
+
return <ReasoningRow item={item} />;
|
|
82
|
+
case "tool-call": {
|
|
83
|
+
const Renderer = toolRegistry.resolve(item);
|
|
84
|
+
return <Renderer item={item} />;
|
|
85
|
+
}
|
|
86
|
+
case "worker":
|
|
87
|
+
return <WorkerRow item={item} onOpenSession={onOpenSession} />;
|
|
88
|
+
case "sandbox":
|
|
89
|
+
return <SandboxRow item={item} />;
|
|
90
|
+
default:
|
|
91
|
+
return assertNever(item);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ReasoningRow({ item }: { item: ReasoningItem }) {
|
|
96
|
+
// Reasoning recedes: a dimmer, lighter-weight title so action rows lead and
|
|
97
|
+
// thought rows sit a half-step back in the hierarchy.
|
|
98
|
+
return (
|
|
99
|
+
<ActivityDisclosure
|
|
100
|
+
icon={<BrainIcon className="size-3.5" />}
|
|
101
|
+
iconTone="muted"
|
|
102
|
+
title={
|
|
103
|
+
item.streaming ? (
|
|
104
|
+
"Thinking"
|
|
105
|
+
) : (
|
|
106
|
+
<span className="font-normal italic text-og-fg-subtle">Thought</span>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
running={item.streaming}
|
|
110
|
+
preview={truncate(item.text, 110)}
|
|
111
|
+
>
|
|
112
|
+
<p className="whitespace-pre-wrap text-og-base leading-6 text-og-fg-muted">{item.text}</p>
|
|
113
|
+
</ActivityDisclosure>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function SandboxRow({ item }: { item: SandboxItem }) {
|
|
118
|
+
return (
|
|
119
|
+
<ActivityDisclosure
|
|
120
|
+
icon={<SquareTerminalIcon className="size-3.5" />}
|
|
121
|
+
iconTone={item.status === "failed" ? "failed" : item.status === "running" ? "running" : "muted"}
|
|
122
|
+
title={toolDisplayName(item.name)}
|
|
123
|
+
running={item.status === "running"}
|
|
124
|
+
failed={item.status === "failed"}
|
|
125
|
+
cancelled={item.status === "cancelled"}
|
|
126
|
+
preview={item.command ?? undefined}
|
|
127
|
+
>
|
|
128
|
+
{item.command ? <PayloadBlock label="Command" value={item.command} /> : null}
|
|
129
|
+
{item.output ? <PayloadBlock label="Output" value={item.output} /> : null}
|
|
130
|
+
</ActivityDisclosure>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Spawned/messaged worker sessions get a first-class card, not a tool row. */
|
|
135
|
+
function WorkerRow({ item, onOpenSession }: { item: WorkerItem; onOpenSession?: ((sessionId: string) => void) | undefined }) {
|
|
136
|
+
const running = item.status === "running";
|
|
137
|
+
const failed = item.status === "failed";
|
|
138
|
+
const cancelled = item.status === "cancelled";
|
|
139
|
+
const title =
|
|
140
|
+
item.action === "spawn"
|
|
141
|
+
? running
|
|
142
|
+
? "Spawning worker"
|
|
143
|
+
: failed
|
|
144
|
+
? "Worker spawn failed"
|
|
145
|
+
: cancelled
|
|
146
|
+
? "Worker interrupted"
|
|
147
|
+
: "Worker spawned"
|
|
148
|
+
: running
|
|
149
|
+
? "Messaging worker"
|
|
150
|
+
: failed
|
|
151
|
+
? "Worker message failed"
|
|
152
|
+
: cancelled
|
|
153
|
+
? "Worker interrupted"
|
|
154
|
+
: "Worker messaged";
|
|
155
|
+
return (
|
|
156
|
+
<div
|
|
157
|
+
className={cn(
|
|
158
|
+
"my-0.5 flex items-start gap-3 rounded-og-md border bg-og-surface-1 p-3",
|
|
159
|
+
failed ? "border-og-status-failed/40" : "border-og-border",
|
|
160
|
+
)}
|
|
161
|
+
>
|
|
162
|
+
<span
|
|
163
|
+
className={cn(
|
|
164
|
+
"mt-0.5 inline-flex size-7 shrink-0 items-center justify-center",
|
|
165
|
+
failed ? "text-og-status-failed" : "text-og-accent",
|
|
166
|
+
)}
|
|
167
|
+
>
|
|
168
|
+
<BotIcon className="size-4" />
|
|
169
|
+
</span>
|
|
170
|
+
<div className="min-w-0 flex-1">
|
|
171
|
+
{/* In-flight state is carried ONLY by the shimmering title (no detached
|
|
172
|
+
pulse badge), matching every other running row in the rail. */}
|
|
173
|
+
<span className={cn("text-og-base font-medium", running ? "og-shimmer-text" : failed ? "text-og-status-failed" : "text-og-fg")}>
|
|
174
|
+
{title}
|
|
175
|
+
</span>
|
|
176
|
+
{item.prompt ? <p className="mt-0.5 truncate text-og-sm text-og-fg-muted">{truncate(item.prompt, 140)}</p> : null}
|
|
177
|
+
{item.workerSessionId ? (
|
|
178
|
+
<p className="mt-1 font-og-mono text-og-xs text-og-fg-subtle">{item.workerSessionId.slice(0, 8)}</p>
|
|
179
|
+
) : null}
|
|
180
|
+
</div>
|
|
181
|
+
{/* Right gutter: failed gets a red chip; cancelled gets a calm "interrupted"
|
|
182
|
+
chip (no dot, no red); a live complete worker shows an "Open session" button. */}
|
|
183
|
+
{failed ? (
|
|
184
|
+
<span className="inline-flex shrink-0 self-center items-center gap-1.5 font-og-mono text-og-xs leading-none text-og-status-failed">
|
|
185
|
+
<span className="size-1.5 rounded-full bg-og-status-failed" />
|
|
186
|
+
failed
|
|
187
|
+
</span>
|
|
188
|
+
) : cancelled ? (
|
|
189
|
+
<span className="og-cancelled-chip shrink-0 self-center font-og-mono text-og-xs leading-none text-og-fg-subtle">
|
|
190
|
+
interrupted
|
|
191
|
+
</span>
|
|
192
|
+
) : item.workerSessionId && onOpenSession ? (
|
|
193
|
+
<button
|
|
194
|
+
type="button"
|
|
195
|
+
onClick={() => item.workerSessionId && onOpenSession(item.workerSessionId)}
|
|
196
|
+
className={cn(
|
|
197
|
+
"shrink-0 self-center rounded-og-sm border border-og-border px-2.5 py-1 text-og-sm font-medium text-og-fg-muted",
|
|
198
|
+
"outline-none transition-colors duration-150 hover:border-og-border-strong hover:text-og-fg",
|
|
199
|
+
"focus-visible:ring-2 focus-visible:ring-og-accent",
|
|
200
|
+
)}
|
|
201
|
+
>
|
|
202
|
+
Open session
|
|
203
|
+
</button>
|
|
204
|
+
) : null}
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/* ----------------------------------------------------------------------------
|
|
4
|
+
Disclosure defaults context
|
|
5
|
+
|
|
6
|
+
A tiny, opt-in context that lets an ancestor seed the INITIAL open state of
|
|
7
|
+
every collapsible in the timeline (ActivityDisclosure rows and TurnSummary
|
|
8
|
+
chips). Its sole intended use is deterministic screenshot capture: a tool can
|
|
9
|
+
force every card open so a headless render shows expanded bodies.
|
|
10
|
+
|
|
11
|
+
It is fully inert in normal app usage. With no provider, the hook returns
|
|
12
|
+
`undefined`, every collapsible keeps its own author-chosen default, and there
|
|
13
|
+
is zero change to how the components look or animate. Mounting the provider
|
|
14
|
+
only changes the SEED of the initial `open` state — Radix still owns the
|
|
15
|
+
open/close transition, so animations are untouched.
|
|
16
|
+
-------------------------------------------------------------------------- */
|
|
17
|
+
|
|
18
|
+
const DisclosureDefaultsContext = createContext<boolean | undefined>(undefined);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Seed the initial open state of every timeline collapsible below this node.
|
|
22
|
+
* Intended for screenshot/test instrumentation only; absent by default.
|
|
23
|
+
*/
|
|
24
|
+
export function DisclosureDefaultsProvider({ defaultOpen, children }: { defaultOpen: boolean; children: ReactNode }) {
|
|
25
|
+
return <DisclosureDefaultsContext.Provider value={defaultOpen}>{children}</DisclosureDefaultsContext.Provider>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The forced initial-open seed from an ancestor {@link DisclosureDefaultsProvider},
|
|
30
|
+
* or `undefined` when none is mounted (the inert, app-default case).
|
|
31
|
+
*/
|
|
32
|
+
export function useForcedDefaultOpen(): boolean | undefined {
|
|
33
|
+
return useContext(DisclosureDefaultsContext);
|
|
34
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* ----------------------------------------------------------------------------
|
|
2
|
+
@opengeni/react — timeline module
|
|
3
|
+
|
|
4
|
+
The session timeline's data + rendering layer:
|
|
5
|
+
- projection.ts raw SessionEvents -> renderable TimelineItems (pure)
|
|
6
|
+
- types.ts the projected item shapes (the data contract)
|
|
7
|
+
- registry.ts the extensible, typed tool-renderer registry
|
|
8
|
+
- tool-renderers per-tool React renderers + the default registry
|
|
9
|
+
- parsers.ts pure provider-shape parsers (exec banner, V4A diff, …)
|
|
10
|
+
- shared.tsx the restraint primitives (ActivityDisclosure, TermBlock, …)
|
|
11
|
+
- tool-diff.tsx V4A GitFileDiff -> the real DiffView/PierreDiff stack
|
|
12
|
+
- screenshot-lightbox.tsx the app-level screenshot lightbox
|
|
13
|
+
|
|
14
|
+
This barrel is the module's public surface. The components here are the
|
|
15
|
+
single source of truth used by both the live app and the component demo.
|
|
16
|
+
-------------------------------------------------------------------------- */
|
|
17
|
+
|
|
18
|
+
// projection
|
|
19
|
+
export { buildTimeline, extractSessionRef, groupTimeline, sessionStatusFromEvents, toolDisplayName } from "./projection";
|
|
20
|
+
|
|
21
|
+
// item types
|
|
22
|
+
export type {
|
|
23
|
+
ActivityItem,
|
|
24
|
+
AgentMessageItem,
|
|
25
|
+
GoalItem,
|
|
26
|
+
NoticeItem,
|
|
27
|
+
ReasoningItem,
|
|
28
|
+
SandboxItem,
|
|
29
|
+
SessionStatusItem,
|
|
30
|
+
TimelineGroup,
|
|
31
|
+
TimelineItem,
|
|
32
|
+
ToolCallItem,
|
|
33
|
+
UserMessageItem,
|
|
34
|
+
WorkerItem,
|
|
35
|
+
} from "./types";
|
|
36
|
+
|
|
37
|
+
// renderer registry
|
|
38
|
+
export { createToolRegistry, rawTypeOf } from "./registry";
|
|
39
|
+
export type {
|
|
40
|
+
CreateToolRegistryOptions,
|
|
41
|
+
ToolRegistry,
|
|
42
|
+
ToolRegistryEntry,
|
|
43
|
+
ToolRenderer,
|
|
44
|
+
ToolRendererProps,
|
|
45
|
+
} from "./registry";
|
|
46
|
+
|
|
47
|
+
// default renderers + registry
|
|
48
|
+
export { createDefaultToolRegistry, defaultToolRegistry } from "./tool-renderers";
|
|
49
|
+
|
|
50
|
+
// the activity rail (the clustered reasoning/tool/worker/sandbox column)
|
|
51
|
+
export { ActivityRail } from "./activity-rail";
|
|
52
|
+
export type { ActivityRailProps } from "./activity-rail";
|
|
53
|
+
|
|
54
|
+
// shared primitives (extension authors compose these)
|
|
55
|
+
export { ActivityDisclosure, BodyNote, MediaEmpty, MediaSkeleton, PayloadBlock, ScreenshotFigure, TermBlock, Thumbnail } from "./shared";
|
|
56
|
+
export type { ActivityDisclosureProps, DisclosureChip } from "./shared";
|
|
57
|
+
|
|
58
|
+
// screenshot lightbox
|
|
59
|
+
export { LightboxProvider, useLightbox, useLightboxOptional } from "./screenshot-lightbox";
|
|
60
|
+
|
|
61
|
+
// disclosure defaults (opt-in initial-open seed; for screenshot/test instrumentation)
|
|
62
|
+
export { DisclosureDefaultsProvider, useForcedDefaultOpen } from "./disclosure-context";
|
|
63
|
+
|
|
64
|
+
// turn-collapse summary chip
|
|
65
|
+
export { TurnSummary } from "./turn-summary";
|
|
66
|
+
export type { TurnOutcome, TurnSummaryProps } from "./turn-summary";
|
|
67
|
+
|
|
68
|
+
// parsers (pure, reusable by custom renderers)
|
|
69
|
+
export {
|
|
70
|
+
applyPatchOps,
|
|
71
|
+
controlCaret,
|
|
72
|
+
execTruncated,
|
|
73
|
+
isApplyPatch,
|
|
74
|
+
isExecSessionLostBanner,
|
|
75
|
+
looksBinary,
|
|
76
|
+
parseExecBannerSessionId,
|
|
77
|
+
parseToolArgs,
|
|
78
|
+
redactSecrets,
|
|
79
|
+
sandboxCommandExitCode,
|
|
80
|
+
stripExecBanner,
|
|
81
|
+
tailPeek,
|
|
82
|
+
unwrapMcpOutput,
|
|
83
|
+
v4aToGitFileDiff,
|
|
84
|
+
} from "./parsers";
|
|
85
|
+
export type { ApplyPatchOperation } from "./parsers";
|