@opengeni/react 5.0.4 → 5.0.5-canary.1

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.
Files changed (60) hide show
  1. package/README.md +3 -0
  2. package/dist/{chunk-SCZ6SUBW.js → chunk-H63SQTGI.js} +1258 -1837
  3. package/dist/chunk-H63SQTGI.js.map +1 -0
  4. package/dist/chunk-HQV2I5HV.js +124 -0
  5. package/dist/chunk-HQV2I5HV.js.map +1 -0
  6. package/dist/chunk-M2HNWZYR.js +835 -0
  7. package/dist/chunk-M2HNWZYR.js.map +1 -0
  8. package/dist/{chunk-Z7WBDB4E.js → chunk-V3WX3TQG.js} +283 -391
  9. package/dist/chunk-V3WX3TQG.js.map +1 -0
  10. package/dist/components/message-timeline.d.ts +3 -1
  11. package/dist/components/sandbox-workspace.d.ts +5 -0
  12. package/dist/composer.js +2 -1
  13. package/dist/connect-chooser.d.ts +1 -0
  14. package/dist/connect-panel.d.ts +2 -1
  15. package/dist/connect.d.ts +7 -0
  16. package/dist/connect.js +792 -183
  17. package/dist/connect.js.map +1 -1
  18. package/dist/connection-catalog.d.ts +33 -0
  19. package/dist/connection-installed.d.ts +15 -0
  20. package/dist/connection-logo.d.ts +6 -0
  21. package/dist/connection-type-picker.d.ts +6 -0
  22. package/dist/device-authorization.d.ts +9 -1
  23. package/dist/index.js +39 -12
  24. package/dist/index.js.map +1 -1
  25. package/dist/plugin-details.d.ts +14 -0
  26. package/dist/plugin-discovery.d.ts +14 -0
  27. package/dist/session-ui.d.ts +3 -0
  28. package/dist/session-ui.js +113 -2
  29. package/dist/session-ui.js.map +1 -1
  30. package/dist/skill-discovery.d.ts +29 -0
  31. package/dist/timeline/activity-rail.d.ts +3 -1
  32. package/dist/timeline/genie-loading.d.ts +24 -0
  33. package/dist/timeline/startup-preference.d.ts +3 -0
  34. package/dist/timeline/startup-timings.d.ts +4 -0
  35. package/package.json +4 -3
  36. package/src/components/message-timeline.tsx +127 -20
  37. package/src/components/sandbox-workspace.tsx +29 -0
  38. package/src/connect-chooser.tsx +81 -21
  39. package/src/connect-panel.tsx +7 -1
  40. package/src/connect.ts +21 -0
  41. package/src/connection-catalog.tsx +153 -0
  42. package/src/connection-installed.tsx +45 -0
  43. package/src/connection-logo.tsx +29 -0
  44. package/src/connection-type-picker.tsx +36 -0
  45. package/src/device-authorization.tsx +9 -1
  46. package/src/hooks/use-codex-accounts.ts +1 -0
  47. package/src/plugin-details.tsx +182 -0
  48. package/src/plugin-discovery.tsx +161 -0
  49. package/src/session-ui.ts +3 -0
  50. package/src/skill-discovery.tsx +152 -0
  51. package/src/timeline/activity-rail.tsx +75 -4
  52. package/src/timeline/genie-loading.tsx +112 -0
  53. package/src/timeline/startup-preference.ts +33 -0
  54. package/src/timeline/startup-timings.tsx +145 -0
  55. package/src/timeline/turn-summary.tsx +8 -3
  56. package/styles/compiled.css +73 -0
  57. package/styles/connect.css +149 -1
  58. package/styles/index.css +15 -0
  59. package/dist/chunk-SCZ6SUBW.js.map +0 -1
  60. package/dist/chunk-Z7WBDB4E.js.map +0 -1
@@ -0,0 +1,14 @@
1
+ import { type PluginDiscoveryItem } from "@opengeni/sdk";
2
+ /** Embeddable plugin overview. Hosts own the dialog and installation actions. */
3
+ export declare function PluginDetails({ item, onInstall, busy, installed, error, connections, onConnect, }: {
4
+ item: PluginDiscoveryItem;
5
+ onInstall?: () => void;
6
+ busy?: boolean;
7
+ installed?: boolean;
8
+ error?: string | null;
9
+ connections?: Record<string, boolean>;
10
+ onConnect?: (server: {
11
+ name: string;
12
+ endpoint: string | null;
13
+ }) => void;
14
+ }): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import type { PluginDiscoveryItem, PluginDiscoveryPage } from "@opengeni/sdk";
2
+ export type PluginDiscoveryProps = {
3
+ client: {
4
+ discoverPlugins(workspaceId: string, options?: {
5
+ query?: string;
6
+ provider?: string;
7
+ offset?: number;
8
+ }): Promise<PluginDiscoveryPage>;
9
+ };
10
+ workspaceId: string;
11
+ query: string;
12
+ onOpen: (item: PluginDiscoveryItem) => void;
13
+ };
14
+ export declare function PluginDiscovery(props: PluginDiscoveryProps): import("react").JSX.Element;
@@ -19,3 +19,6 @@ export type { QueueSurfaceProps } from "./components/queue-surface.js";
19
19
  export { SessionChrome, sessionChromeGoalPillExplanation, sessionChromeGoalPillLabel, sessionChromeGoalPillState, } from "./components/session-chrome.js";
20
20
  export type { SessionChromeAgentsSignal, SessionChromeProps, SessionChromeSignalId, SessionChromeSignalTone, } from "./components/session-chrome.js";
21
21
  export { SessionCommandsPanel } from "./components/session-commands-panel.js";
22
+ export { StartupTimings } from "./timeline/startup-timings.js";
23
+ export { useStartupDetails, setStartupDetails } from "./timeline/startup-preference.js";
24
+ export type { GenieLoadingOptions } from "./timeline/genie-loading.js";
@@ -13,15 +13,19 @@ import {
13
13
  sessionChromeGoalPillExplanation,
14
14
  sessionChromeGoalPillLabel,
15
15
  sessionChromeGoalPillState,
16
+ setStartupDetails,
17
+ useStartupDetails,
16
18
  userMessageLikelyNeedsDisclosure
17
- } from "./chunk-SCZ6SUBW.js";
19
+ } from "./chunk-H63SQTGI.js";
18
20
  import "./chunk-YBM6CQRU.js";
19
21
  import "./chunk-5M6VTFJ5.js";
20
22
  import "./chunk-ZYRGEKWS.js";
21
23
  import "./chunk-NOSYBS2D.js";
22
24
  import "./chunk-PAJU5KKL.js";
25
+ import "./chunk-M2HNWZYR.js";
23
26
  import "./chunk-JVYQRRC4.js";
24
- import "./chunk-Z7WBDB4E.js";
27
+ import "./chunk-V3WX3TQG.js";
28
+ import "./chunk-HQV2I5HV.js";
25
29
  import "./chunk-R4KRSFGA.js";
26
30
  import "./chunk-VZTQ73XT.js";
27
31
  import {
@@ -33,6 +37,110 @@ import "./chunk-KZ73RL76.js";
33
37
  import "./chunk-NEXLLTP4.js";
34
38
  import "./chunk-VRBL3F5E.js";
35
39
  import "./chunk-22KP6LR5.js";
40
+
41
+ // src/timeline/startup-timings.tsx
42
+ import { jsx, jsxs } from "react/jsx-runtime";
43
+ var LABELS = {
44
+ queue: "Worker queue",
45
+ sandbox: "Sandbox",
46
+ rig: "Rig",
47
+ repository: "Repository",
48
+ files: "Files",
49
+ tools: "Tools",
50
+ model_preparation: "Runtime & model request",
51
+ provider_first_byte: "First model response"
52
+ };
53
+ function StartupTimings({ phases }) {
54
+ if (!phases.length)
55
+ return /* @__PURE__ */ jsx("p", { className: "text-og-sm text-og-fg-subtle", children: "No startup timings recorded yet." });
56
+ const byTurn = /* @__PURE__ */ new Map();
57
+ for (const phase of phases) {
58
+ const group = byTurn.get(phase.turnId) ?? [];
59
+ group.push(phase);
60
+ byTurn.set(phase.turnId, group);
61
+ }
62
+ const turns = [...byTurn.entries()].map(([turnId, turnPhases]) => ({
63
+ turnId,
64
+ phases: turnPhases,
65
+ startedAt: turnPhases.reduce(
66
+ (earliest, phase) => phase.startedAt < earliest ? phase.startedAt : earliest,
67
+ turnPhases[0].startedAt
68
+ )
69
+ })).sort((a, b) => b.startedAt.localeCompare(a.startedAt));
70
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
71
+ /* @__PURE__ */ jsx("p", { className: "text-og-xs text-og-fg-subtle", children: "Each turn has its own startup. Phases can overlap; durations are not additive." }),
72
+ turns.map((turn, index) => /* @__PURE__ */ jsx(
73
+ StartupTurn,
74
+ {
75
+ ...turn,
76
+ latest: index === 0 && turn.turnId !== null
77
+ },
78
+ turn.turnId ?? "unassigned"
79
+ ))
80
+ ] });
81
+ }
82
+ function StartupTurn({
83
+ turnId,
84
+ phases,
85
+ startedAt,
86
+ latest
87
+ }) {
88
+ const status = phases.some((phase) => phase.status === "failed") ? "Failed" : phases.some((phase) => phase.status === "running") ? "In progress" : phases.some((phase) => phase.status === "cancelled") ? "Interrupted" : "Ready";
89
+ return /* @__PURE__ */ jsxs("details", { open: latest, className: "group border-b border-og-border pb-3", children: [
90
+ /* @__PURE__ */ jsxs("summary", { className: "cursor-pointer rounded py-3 text-og-sm text-og-fg-muted focus-visible:outline-2 focus-visible:outline-og-accent", children: [
91
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: turnId === null ? "Unassigned events" : latest ? "Latest turn" : "Earlier turn" }),
92
+ /* @__PURE__ */ jsx("span", { className: "ml-2 text-og-xs text-og-fg-subtle", children: new Date(startedAt).toLocaleTimeString([], {
93
+ hour: "2-digit",
94
+ minute: "2-digit",
95
+ second: "2-digit"
96
+ }) }),
97
+ /* @__PURE__ */ jsx(
98
+ "span",
99
+ {
100
+ className: status === "Failed" ? "ml-2 text-og-xs text-og-status-failed" : "ml-2 text-og-xs text-og-fg-subtle",
101
+ children: status
102
+ }
103
+ ),
104
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block break-all pl-4 font-og-mono text-og-xs text-og-fg-subtle", children: turnId ?? "No turn ID recorded" })
105
+ ] }),
106
+ /* @__PURE__ */ jsxs("div", { className: "pb-2", children: [
107
+ /* @__PURE__ */ jsxs("p", { className: "text-og-xs text-og-fg-subtle", children: [
108
+ "Started ",
109
+ /* @__PURE__ */ jsx("time", { dateTime: startedAt, children: new Date(startedAt).toLocaleString() })
110
+ ] }),
111
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-left text-og-xs", children: [
112
+ /* @__PURE__ */ jsxs("caption", { className: "sr-only", children: [
113
+ "Startup phases for ",
114
+ turnId ?? "unassigned events"
115
+ ] }),
116
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-og-border text-og-fg-subtle", children: [
117
+ /* @__PURE__ */ jsx("th", { scope: "col", className: "py-2 font-medium", children: "Phase" }),
118
+ /* @__PURE__ */ jsx("th", { scope: "col", className: "px-3 py-2 font-medium", children: "Status" }),
119
+ /* @__PURE__ */ jsx("th", { scope: "col", className: "py-2 text-right font-medium", children: "Duration" })
120
+ ] }) }),
121
+ /* @__PURE__ */ jsx("tbody", { children: phases.map((phase) => /* @__PURE__ */ jsxs(
122
+ "tr",
123
+ {
124
+ className: "border-b border-og-border/40",
125
+ title: `Turn: ${phase.turnId ?? "unknown"} \xB7 Started: ${phase.startedAt}`,
126
+ children: [
127
+ /* @__PURE__ */ jsx("th", { scope: "row", className: "py-2.5 font-normal text-og-fg-muted", children: LABELS[phase.phase] }),
128
+ /* @__PURE__ */ jsx(
129
+ "td",
130
+ {
131
+ className: `px-3 py-2.5 ${phase.status === "failed" ? "text-og-status-failed" : "text-og-fg-subtle"}`,
132
+ children: phase.status === "running" ? "In progress" : phase.status
133
+ }
134
+ ),
135
+ /* @__PURE__ */ jsx("td", { className: "py-2.5 text-right font-og-mono tabular-nums text-og-fg-muted", children: phase.durationMs === null ? "\u2014" : phase.durationMs < 1e3 ? `${Math.round(phase.durationMs)} ms` : `${(phase.durationMs / 1e3).toFixed(1)} s` })
136
+ ]
137
+ },
138
+ phase.id
139
+ )) })
140
+ ] }) })
141
+ ] })
142
+ ] });
143
+ }
36
144
  export {
37
145
  ApprovalSurface,
38
146
  BUILT_IN_TURN_SUMMARY_FACET_IDS,
@@ -43,12 +151,15 @@ export {
43
151
  SessionChrome,
44
152
  SessionCommandsPanel,
45
153
  SessionConversation,
154
+ StartupTimings,
46
155
  TimelineRow,
47
156
  UserMessageBody,
48
157
  createOlderHistoryLoadReceipt,
49
158
  sessionChromeGoalPillExplanation,
50
159
  sessionChromeGoalPillLabel,
51
160
  sessionChromeGoalPillState,
161
+ setStartupDetails,
162
+ useStartupDetails,
52
163
  userMessageLikelyNeedsDisclosure
53
164
  };
54
165
  //# sourceMappingURL=session-ui.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"sources":["../src/timeline/startup-timings.tsx"],"sourcesContent":["import type { StartupPhaseItem } from \"./types\";\n\nconst LABELS: Record<StartupPhaseItem[\"phase\"], string> = {\n queue: \"Worker queue\",\n sandbox: \"Sandbox\",\n rig: \"Rig\",\n repository: \"Repository\",\n files: \"Files\",\n tools: \"Tools\",\n model_preparation: \"Runtime & model request\",\n provider_first_byte: \"First model response\",\n};\n\nexport function StartupTimings({ phases }: { phases: StartupPhaseItem[] }) {\n if (!phases.length)\n return <p className=\"text-og-sm text-og-fg-subtle\">No startup timings recorded yet.</p>;\n const byTurn = new Map<string | null, StartupPhaseItem[]>();\n for (const phase of phases) {\n const group = byTurn.get(phase.turnId) ?? [];\n group.push(phase);\n byTurn.set(phase.turnId, group);\n }\n const turns = [...byTurn.entries()]\n .map(([turnId, turnPhases]) => ({\n turnId,\n phases: turnPhases,\n startedAt: turnPhases.reduce(\n (earliest, phase) => (phase.startedAt < earliest ? phase.startedAt : earliest),\n turnPhases[0]!.startedAt,\n ),\n }))\n .sort((a, b) => b.startedAt.localeCompare(a.startedAt));\n return (\n <div className=\"space-y-3\">\n <p className=\"text-og-xs text-og-fg-subtle\">\n Each turn has its own startup. Phases can overlap; durations are not additive.\n </p>\n {turns.map((turn, index) => (\n <StartupTurn\n key={turn.turnId ?? \"unassigned\"}\n {...turn}\n latest={index === 0 && turn.turnId !== null}\n />\n ))}\n </div>\n );\n}\n\nfunction StartupTurn({\n turnId,\n phases,\n startedAt,\n latest,\n}: {\n turnId: string | null;\n phases: StartupPhaseItem[];\n startedAt: string;\n latest: boolean;\n}) {\n const status = phases.some((phase) => phase.status === \"failed\")\n ? \"Failed\"\n : phases.some((phase) => phase.status === \"running\")\n ? \"In progress\"\n : phases.some((phase) => phase.status === \"cancelled\")\n ? \"Interrupted\"\n : \"Ready\";\n return (\n <details open={latest} className=\"group border-b border-og-border pb-3\">\n <summary className=\"cursor-pointer rounded py-3 text-og-sm text-og-fg-muted focus-visible:outline-2 focus-visible:outline-og-accent\">\n <span className=\"font-medium\">\n {turnId === null ? \"Unassigned events\" : latest ? \"Latest turn\" : \"Earlier turn\"}\n </span>\n <span className=\"ml-2 text-og-xs text-og-fg-subtle\">\n {new Date(startedAt).toLocaleTimeString([], {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n })}\n </span>\n <span\n className={\n status === \"Failed\"\n ? \"ml-2 text-og-xs text-og-status-failed\"\n : \"ml-2 text-og-xs text-og-fg-subtle\"\n }\n >\n {status}\n </span>\n <span className=\"mt-1 block break-all pl-4 font-og-mono text-og-xs text-og-fg-subtle\">\n {turnId ?? \"No turn ID recorded\"}\n </span>\n </summary>\n <div className=\"pb-2\">\n <p className=\"text-og-xs text-og-fg-subtle\">\n Started <time dateTime={startedAt}>{new Date(startedAt).toLocaleString()}</time>\n </p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-left text-og-xs\">\n <caption className=\"sr-only\">\n Startup phases for {turnId ?? \"unassigned events\"}\n </caption>\n <thead>\n <tr className=\"border-b border-og-border text-og-fg-subtle\">\n <th scope=\"col\" className=\"py-2 font-medium\">\n Phase\n </th>\n <th scope=\"col\" className=\"px-3 py-2 font-medium\">\n Status\n </th>\n <th scope=\"col\" className=\"py-2 text-right font-medium\">\n Duration\n </th>\n </tr>\n </thead>\n <tbody>\n {phases.map((phase) => (\n <tr\n key={phase.id}\n className=\"border-b border-og-border/40\"\n title={`Turn: ${phase.turnId ?? \"unknown\"} · Started: ${phase.startedAt}`}\n >\n <th scope=\"row\" className=\"py-2.5 font-normal text-og-fg-muted\">\n {LABELS[phase.phase]}\n </th>\n <td\n className={`px-3 py-2.5 ${phase.status === \"failed\" ? \"text-og-status-failed\" : \"text-og-fg-subtle\"}`}\n >\n {phase.status === \"running\" ? \"In progress\" : phase.status}\n </td>\n <td className=\"py-2.5 text-right font-og-mono tabular-nums text-og-fg-muted\">\n {phase.durationMs === null\n ? \"—\"\n : phase.durationMs < 1000\n ? `${Math.round(phase.durationMs)} ms`\n : `${(phase.durationMs / 1000).toFixed(1)} s`}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n </details>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,29 @@
1
+ export type SkillDiscoveryItem = {
2
+ id: string;
3
+ name: string;
4
+ source: string;
5
+ installs: number;
6
+ url: string;
7
+ };
8
+ export type SkillDiscoveryPage = {
9
+ items: SkillDiscoveryItem[];
10
+ nextCursor: null;
11
+ };
12
+ export type SkillDiscoveryClient = {
13
+ searchPublicSkills(workspaceId: string, query: string): Promise<SkillDiscoveryPage>;
14
+ };
15
+ export type SkillDiscoveryProps = {
16
+ installedSkills?: readonly {
17
+ name: string;
18
+ repositoryUrl: string;
19
+ sourceUrl: string;
20
+ }[];
21
+ client: SkillDiscoveryClient;
22
+ workspaceId: string;
23
+ query: string;
24
+ onImport: (url: string) => void;
25
+ canManage: boolean;
26
+ onSearch?: (() => void) | undefined;
27
+ };
28
+ /** Host-owned query and import flow; browse presentation is reusable independently. */
29
+ export declare function SkillDiscovery(props: SkillDiscoveryProps): import("react").JSX.Element;
@@ -2,6 +2,8 @@ import type { RetainedArtifactLoader, RetainedScreenshotLoader, ToolRegistry } f
2
2
  import type { ActivityItem } from "./types.js";
3
3
  export type ActivityRailProps = {
4
4
  items: ActivityItem[];
5
+ /** The owning turn remains active between individual phase receipts. */
6
+ startupActive?: boolean;
5
7
  /** Renderer registry for tool calls. Defaults to {@link defaultToolRegistry}. */
6
8
  toolRegistry?: ToolRegistry | undefined;
7
9
  /** Drill into a spawned worker session. */
@@ -19,4 +21,4 @@ export type ActivityRailProps = {
19
21
  bare?: boolean | undefined;
20
22
  className?: string | undefined;
21
23
  };
22
- export declare function ActivityRail({ items, toolRegistry, onOpenSession, onMemoryClick, loadRetainedScreenshot, loadRetainedArtifact, bare, className, }: ActivityRailProps): import("react").JSX.Element;
24
+ export declare function ActivityRail({ items, startupActive, toolRegistry, onOpenSession, onMemoryClick, loadRetainedScreenshot, loadRetainedArtifact, bare, className, }: ActivityRailProps): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { type ReactNode } from "react";
2
+ export type GenieLoadingRenderProps = {
3
+ startedAt: string;
4
+ detailsOpen: boolean;
5
+ onShowDetails: () => void;
6
+ };
7
+ export type GenieLoadingOptions = {
8
+ /** Replace the visual while preserving SDK loading visibility and transitions. */
9
+ render?: (props: GenieLoadingRenderProps) => ReactNode;
10
+ phrases?: readonly string[];
11
+ /** Public options stay independent of the renderer's declaration layout. */
12
+ orb?: {
13
+ state?: "working" | "searching" | "solving" | "listening" | "connecting" | "weaving" | "composing" | "breathing" | "shaping";
14
+ size?: 64 | 20;
15
+ speed?: number;
16
+ };
17
+ };
18
+ export declare const GenieLoadingOptionsContext: import("react").Context<GenieLoadingOptions | undefined>;
19
+ /** Decorative copy never substitutes for a failure or claims measurable progress. */
20
+ export declare function GenieLoading({ startedAt, onShowDetails, detailsOpen, }: {
21
+ startedAt: string;
22
+ onShowDetails: () => void;
23
+ detailsOpen?: boolean;
24
+ }): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
@@ -0,0 +1,3 @@
1
+ export declare function setStartupDetails(value: boolean): void;
2
+ /** Presentation preference only. Startup evidence is always retained. */
3
+ export declare function useStartupDetails(): boolean;
@@ -0,0 +1,4 @@
1
+ import type { StartupPhaseItem } from "./types.js";
2
+ export declare function StartupTimings({ phases }: {
3
+ phases: StartupPhaseItem[];
4
+ }): import("react").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/react",
3
- "version": "5.0.4",
3
+ "version": "5.0.5-canary.1",
4
4
  "description": "React hooks and styled components for OpenGeni: live session streaming, chat composer, message timeline, session status, and fleet views — token-themed (CSS variables), dark-first, built on Tailwind v4 + Radix + Motion.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -133,8 +133,8 @@
133
133
  "@dnd-kit/utilities": "3.2.2",
134
134
  "@fontsource-variable/noto-sans-arabic": "5.2.10",
135
135
  "@fontsource-variable/noto-sans-jp": "5.2.10",
136
- "@opengeni/connect": "^0.2.0",
137
- "@opengeni/sdk": "^5.0.3",
136
+ "@opengeni/connect": "^0.2.0-canary.3",
137
+ "@opengeni/sdk": "^5.0.5-canary.1",
138
138
  "clsx": "^2.1.1",
139
139
  "dequal": "2.0.3",
140
140
  "lucide-react": "^1.8.0",
@@ -144,6 +144,7 @@
144
144
  "react-resizable-panels": "^4.11.2",
145
145
  "remark-gfm": "^4.0.1",
146
146
  "tailwind-merge": "^3.5.0",
147
+ "thinking-orbs": "^0.3.1",
147
148
  "virtua": "^0.49.2"
148
149
  },
149
150
  "peerDependencies": {
@@ -1,4 +1,6 @@
1
+ import { GenieLoadingOptionsContext, type GenieLoadingOptions } from "../timeline/genie-loading";
1
2
  import { ChildSessionLink } from "./child-session-link";
3
+ import { useStartupDetails } from "../timeline/startup-preference";
2
4
  import type {
3
5
  DraftTimelineAnnotation,
4
6
  MediaGenerationResult,
@@ -26,7 +28,7 @@ import {
26
28
  XCircleIcon,
27
29
  } from "lucide-react";
28
30
  import type { ComponentType } from "react";
29
- import { AnimatePresence, motion } from "motion/react";
31
+ import { AnimatePresence, motion, useReducedMotion } from "motion/react";
30
32
  import { Collapsible } from "radix-ui";
31
33
  import {
32
34
  Component,
@@ -191,6 +193,7 @@ export type MessageTimelineProps = {
191
193
  */
192
194
  computeLabel?: string | null | undefined;
193
195
  /** Customize collapsed turn facets for this timeline instance. */
196
+ genieLoading?: GenieLoadingOptions | undefined;
194
197
  turnSummary?: TurnSummaryOptions | undefined;
195
198
  /** Follow new events when pinned to the bottom. Defaults to true. */
196
199
  autoFollow?: boolean | undefined;
@@ -417,6 +420,7 @@ export function MessageTimeline({
417
420
  loadRetainedArtifact,
418
421
  loadVideoArtifactPlayback,
419
422
  computeLabel = null,
423
+ genieLoading,
420
424
  turnSummary,
421
425
  autoFollow = true,
422
426
  onAnnotate,
@@ -590,6 +594,20 @@ export function MessageTimeline({
590
594
  previousBulkFirstKeyRef.current !== firstGroupKey;
591
595
  const bulkRender = allGroups.length > 0 && (bulkActive || firstKeyChangedForBulk);
592
596
  const groups = useStableTimelineGroupKeys(allGroups, !bulkRender);
597
+ const turnsWithOutput = useMemo(
598
+ () =>
599
+ new Set(
600
+ resolvedItems.flatMap((item) =>
601
+ "turnId" in item &&
602
+ item.turnId &&
603
+ (item.kind === "tool-call" ||
604
+ ((item.kind === "agent-message" || item.kind === "reasoning") && item.text.trim()))
605
+ ? [item.turnId]
606
+ : [],
607
+ ),
608
+ ),
609
+ [resolvedItems],
610
+ );
593
611
 
594
612
  const applyCanSkipTipCatchup = useCallback((value: boolean) => {
595
613
  if (canSkipTipCatchupRef.current !== value) {
@@ -919,6 +937,7 @@ export function MessageTimeline({
919
937
  loadRetainedScreenshot,
920
938
  loadRetainedArtifact,
921
939
  loadVideoArtifactPlayback,
940
+ genieLoading,
922
941
  turnSummary,
923
942
  },
924
943
  }),
@@ -934,6 +953,7 @@ export function MessageTimeline({
934
953
  renderMessageText,
935
954
  resolveProviderLogo,
936
955
  toolRegistry,
956
+ genieLoading,
937
957
  turnSummary,
938
958
  userMessageDisclosureContext,
939
959
  ],
@@ -2007,6 +2027,12 @@ export function MessageTimeline({
2007
2027
  groupKey={key}
2008
2028
  group={group}
2009
2029
  nextGroup={groups[index + 1]?.group}
2030
+ startupDismissed={
2031
+ group.kind === "activity" &&
2032
+ group.items.some(
2033
+ (item) => item.turnId && turnsWithOutput.has(item.turnId),
2034
+ )
2035
+ }
2010
2036
  entranceEnabled={entranceEnabled}
2011
2037
  liveEntranceEnabled={
2012
2038
  group.kind === "activity" ? !bulkRender : undefined
@@ -2178,7 +2204,24 @@ function useStableTimelineGroupKeys(
2178
2204
  // Retain only same-kind matches. Activity → turn wrap must NOT keep the
2179
2205
  // activity chip's React key: that reused a collapsed TurnSummary and
2180
2206
  // skipped the settle beat (insta-collapse / content flash).
2181
- if (previous && previous.group.kind === group.kind && !usedKeys.has(previous.key)) {
2207
+ const startupCompletion =
2208
+ previous?.group.kind === "activity" &&
2209
+ previous.group.items.every(
2210
+ (item) =>
2211
+ item.kind === "startup-phase" || (item.kind === "reasoning" && !item.text.trim()),
2212
+ ) &&
2213
+ group.kind === "turn" &&
2214
+ group.outcome === "complete" &&
2215
+ group.groups.every(
2216
+ (child) =>
2217
+ child.kind === "activity" &&
2218
+ child.items.every((item) => item.kind === "startup-phase"),
2219
+ );
2220
+ if (
2221
+ previous &&
2222
+ (previous.group.kind === group.kind || startupCompletion) &&
2223
+ !usedKeys.has(previous.key)
2224
+ ) {
2182
2225
  retainedGroup = previous;
2183
2226
  break;
2184
2227
  }
@@ -2298,6 +2341,7 @@ type TimelineGroupEntryProps = {
2298
2341
  groupKey: string;
2299
2342
  group: TimelineGroup;
2300
2343
  nextGroup?: TimelineGroup | undefined;
2344
+ startupDismissed: boolean;
2301
2345
  entranceEnabled: boolean;
2302
2346
  liveEntranceEnabled?: boolean | undefined;
2303
2347
  context: TimelineGroupEntryContext;
@@ -2320,6 +2364,7 @@ type TimelineGroupBehaviorProps = {
2320
2364
  loadRetainedScreenshot: MessageTimelineProps["loadRetainedScreenshot"];
2321
2365
  loadRetainedArtifact: MessageTimelineProps["loadRetainedArtifact"];
2322
2366
  loadVideoArtifactPlayback: MessageTimelineProps["loadVideoArtifactPlayback"];
2367
+ genieLoading: MessageTimelineProps["genieLoading"];
2323
2368
  turnSummary: MessageTimelineProps["turnSummary"];
2324
2369
  };
2325
2370
 
@@ -2332,10 +2377,12 @@ const TimelineGroupEntry = memo(function TimelineGroupEntry({
2332
2377
  groupKey,
2333
2378
  group,
2334
2379
  nextGroup,
2380
+ startupDismissed,
2335
2381
  entranceEnabled,
2336
2382
  liveEntranceEnabled,
2337
2383
  context,
2338
2384
  }: TimelineGroupEntryProps) {
2385
+ const reducedMotion = useReducedMotion();
2339
2386
  const { behavior, userMessageDisclosureContext } = context;
2340
2387
  const contextCompactionCount =
2341
2388
  group.kind === "turn"
@@ -2347,23 +2394,45 @@ const TimelineGroupEntry = memo(function TimelineGroupEntry({
2347
2394
  ? 1
2348
2395
  : 0;
2349
2396
  return (
2350
- <div data-og-timeline-group-anchor="" data-og-group-key={groupKey}>
2351
- <EntranceAnimationProvider value={entranceEnabled} liveValue={liveEntranceEnabled}>
2352
- <TimelineGroupRenderBoundary resetKeys={[group, behavior]}>
2353
- <UserMessageDisclosureProvider value={userMessageDisclosureContext}>
2354
- <TimelineGroupView
2355
- {...behavior}
2356
- group={group}
2357
- foldLiveCluster={isAgentProgress(nextGroup)}
2358
- trailingAgentText={trailingAgentTextAfterTurn(group, nextGroup)}
2359
- contextCompactionCount={
2360
- contextCompactionCount > 0 ? contextCompactionCount : undefined
2361
- }
2362
- />
2363
- </UserMessageDisclosureProvider>
2364
- </TimelineGroupRenderBoundary>
2365
- </EntranceAnimationProvider>
2366
- </div>
2397
+ <GenieLoadingOptionsContext.Provider value={behavior.genieLoading}>
2398
+ <div data-og-timeline-group-anchor="" data-og-group-key={groupKey}>
2399
+ <EntranceAnimationProvider value={entranceEnabled} liveValue={liveEntranceEnabled}>
2400
+ <TimelineGroupRenderBoundary resetKeys={[group, behavior]}>
2401
+ <UserMessageDisclosureProvider value={userMessageDisclosureContext}>
2402
+ <AnimatePresence initial={false}>
2403
+ <motion.div
2404
+ key={
2405
+ !startupDismissed &&
2406
+ group.kind === "activity" &&
2407
+ group.items.every(
2408
+ (item) =>
2409
+ item.kind === "startup-phase" ||
2410
+ (item.kind === "reasoning" && !item.text.trim()),
2411
+ )
2412
+ ? "preparation"
2413
+ : "content"
2414
+ }
2415
+ initial={false}
2416
+ exit={{ opacity: 0, height: 0 }}
2417
+ transition={{ duration: reducedMotion ? 0 : 0.2 }}
2418
+ >
2419
+ <TimelineGroupView
2420
+ {...behavior}
2421
+ group={group}
2422
+ foldLiveCluster={isAgentProgress(nextGroup)}
2423
+ startupDismissed={startupDismissed}
2424
+ trailingAgentText={trailingAgentTextAfterTurn(group, nextGroup)}
2425
+ contextCompactionCount={
2426
+ contextCompactionCount > 0 ? contextCompactionCount : undefined
2427
+ }
2428
+ />
2429
+ </motion.div>
2430
+ </AnimatePresence>
2431
+ </UserMessageDisclosureProvider>
2432
+ </TimelineGroupRenderBoundary>
2433
+ </EntranceAnimationProvider>
2434
+ </div>
2435
+ </GenieLoadingOptionsContext.Provider>
2367
2436
  );
2368
2437
  });
2369
2438
 
@@ -2389,6 +2458,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
2389
2458
  insideTurn = false,
2390
2459
  nestClusterChips = false,
2391
2460
  foldLiveCluster = false,
2461
+ startupDismissed = false,
2392
2462
  trailingAgentText,
2393
2463
  contextCompactionCount,
2394
2464
  }: {
@@ -2412,6 +2482,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
2412
2482
  behind a neutral chip — the one place activity without an outcome still
2413
2483
  folds, bounding the DOM of days-long autonomous turns. */
2414
2484
  foldLiveCluster?: boolean;
2485
+ startupDismissed?: boolean;
2415
2486
  /** Rendering inside an expanded turn group: the outer chip already owns the
2416
2487
  failure surface, so nested chips stay tinted but quiet (no repeated
2417
2488
  failure text, no auto-open) — one loud error, N calm sub-expands. */
@@ -2432,6 +2503,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
2432
2503
  /** Secondary chip facet when this fold sits next to a compaction landmark. */
2433
2504
  contextCompactionCount?: number | undefined;
2434
2505
  }) {
2506
+ const startupDetails = useStartupDetails();
2435
2507
  const enter = useEntranceAnimation();
2436
2508
  const settleChrome = useTurnSettleOpen();
2437
2509
  const foldMemory = useFoldMemory();
@@ -2463,6 +2535,29 @@ const TimelineGroupView = memo(function TimelineGroupView({
2463
2535
  group.kind === "turn" ? !!(enter && !insideTurn && !turnDefaultOpen) : liveActivitySettle;
2464
2536
  switch (group.kind) {
2465
2537
  case "activity":
2538
+ // Preparation is one quiet surface, not a fold with eight technical steps.
2539
+ if (
2540
+ !startupDetails &&
2541
+ !insideTurn &&
2542
+ !group.outcome &&
2543
+ group.items.every(
2544
+ (item) =>
2545
+ item.kind === "startup-phase" || (item.kind === "reasoning" && !item.text.trim()),
2546
+ )
2547
+ ) {
2548
+ return (
2549
+ <ActivityRail
2550
+ items={group.items}
2551
+ startupActive={!startupDismissed && !foldLiveCluster}
2552
+ bare
2553
+ toolRegistry={toolRegistry}
2554
+ onOpenSession={onOpenSession}
2555
+ onMemoryClick={onMemoryClick}
2556
+ loadRetainedScreenshot={loadRetainedScreenshot}
2557
+ loadRetainedArtifact={loadRetainedArtifact}
2558
+ />
2559
+ );
2560
+ }
2466
2561
  if (insideTurn) {
2467
2562
  // Nested chips whenever the parent has ≥2 clusters. During outer settle
2468
2563
  // chrome they stay force-open so structure is visible and height stays
@@ -2542,6 +2637,18 @@ const TimelineGroupView = memo(function TimelineGroupView({
2542
2637
  );
2543
2638
  case "turn": {
2544
2639
  const activityItems = flattenActivityItems(group.groups);
2640
+ if (
2641
+ !startupDetails &&
2642
+ group.outcome === "complete" &&
2643
+ group.groups.every(
2644
+ (child) =>
2645
+ child.kind === "activity" &&
2646
+ child.items.every(
2647
+ (item) => item.kind === "startup-phase" && item.status === "complete",
2648
+ ),
2649
+ )
2650
+ )
2651
+ return <ActivityRail items={activityItems} startupActive={false} bare />;
2545
2652
  // Second-layer chips only when there are natural multi-cluster seams —
2546
2653
  // otherwise the outer turn chip alone is enough ("N steps" wrapping one
2547
2654
  // more "N steps" was the redundant double fold).
@@ -2727,7 +2834,7 @@ function isAgentProgress(next: TimelineGroup | undefined): boolean {
2727
2834
  return (
2728
2835
  next.kind === "activity" ||
2729
2836
  next.kind === "turn" ||
2730
- (next.kind === "item" && next.item.kind === "agent-message")
2837
+ (next.kind === "item" && next.item.kind === "agent-message" && next.item.text.trim().length > 0)
2731
2838
  );
2732
2839
  }
2733
2840
 
@@ -1002,6 +1002,8 @@ export type SandboxWorkspaceProps = ClientOverride & {
1002
1002
  * omitted the workbench decides from the durable workspace capture. */
1003
1003
  initialTab?: string | undefined;
1004
1004
  onActiveTabChange?: ((activeTab: string) => void) | undefined;
1005
+ /** Open a built-in or host-injected tab. A new requestId reopens the same tab. */
1006
+ openTabRequest?: { tab: string; requestId: number } | null | undefined;
1005
1007
  initialFilePath?: string | null | undefined;
1006
1008
  onFilePathChange?: ((path: string | null) => void) | undefined;
1007
1009
  /** Presentation-only filter for Files tree nodes and selected-file viewing. */
@@ -1061,6 +1063,7 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
1061
1063
  trailingTabs,
1062
1064
  initialTab,
1063
1065
  onActiveTabChange,
1066
+ openTabRequest,
1064
1067
  initialFilePath,
1065
1068
  onFilePathChange,
1066
1069
  isFileNodeVisible,
@@ -1102,6 +1105,32 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
1102
1105
  const nextFileRequestId = useRef(0);
1103
1106
  const nextComputerRequestId = useRef(0);
1104
1107
  const selectedTab = storedSelection?.sessionId === sessionId ? storedSelection.tab : null;
1108
+ const handledTabRequest = useRef<{ sessionId: string; requestId: number } | null>(null);
1109
+ useEffect(() => {
1110
+ if (
1111
+ !openTabRequest ||
1112
+ (handledTabRequest.current?.sessionId === sessionId &&
1113
+ handledTabRequest.current.requestId === openTabRequest.requestId)
1114
+ )
1115
+ return;
1116
+ const available =
1117
+ [...(leadingTabs ?? []), ...(trailingTabs ?? [])].some(
1118
+ (tab) => tab.id === openTabRequest.tab,
1119
+ ) || (surfaces ?? WORKBENCH_SURFACES).some((tab) => tab === openTabRequest.tab);
1120
+ if (!available) return;
1121
+ handledTabRequest.current = { sessionId, requestId: openTabRequest.requestId };
1122
+ setStoredSelection({ sessionId, tab: openTabRequest.tab });
1123
+ onActiveTabChange?.(openTabRequest.tab);
1124
+ onCollapsedChange?.(false);
1125
+ }, [
1126
+ openTabRequest,
1127
+ sessionId,
1128
+ leadingTabs,
1129
+ trailingTabs,
1130
+ surfaces,
1131
+ onActiveTabChange,
1132
+ onCollapsedChange,
1133
+ ]);
1105
1134
  const activeTabHint = selectedTab ?? initialTab ?? leadingTabs?.[0]?.id ?? null;
1106
1135
  const openFile = useCallback(
1107
1136
  (path: string, line?: number | null) => {