@opengeni/react 0.57.0 → 2.2.0-canary.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 +36 -11
- package/dist/artifacts-spreadsheet.js +1 -1
- package/dist/artifacts.js +1 -1
- package/dist/browser-viewer-GXUICIAG.js +14 -0
- package/dist/{chunk-R3AZOAB5.js → chunk-4MDLEHXL.js} +9 -115
- package/dist/chunk-4MDLEHXL.js.map +1 -0
- package/dist/{chunk-Y7OEDUJ5.js → chunk-5RZN3DD5.js} +1014 -420
- package/dist/chunk-5RZN3DD5.js.map +1 -0
- package/dist/chunk-5YK2ZDIG.js +116 -0
- package/dist/chunk-5YK2ZDIG.js.map +1 -0
- package/dist/chunk-H5YGYNXS.js +329 -0
- package/dist/chunk-H5YGYNXS.js.map +1 -0
- package/dist/{chunk-7WCLAEFV.js → chunk-ICA6R6TW.js} +2 -2
- package/dist/{chunk-7WCLAEFV.js.map → chunk-ICA6R6TW.js.map} +1 -1
- package/dist/{chunk-IGC6A2ZI.js → chunk-JOAVA2TW.js} +589 -217
- package/dist/chunk-JOAVA2TW.js.map +1 -0
- package/dist/{chunk-5UQPNLIO.js → chunk-LTCHJ4MS.js} +9 -9
- package/dist/chunk-LTCHJ4MS.js.map +1 -0
- package/dist/{chunk-C5ZPSSS5.js → chunk-NEXLLTP4.js} +2 -1
- package/dist/{chunk-C5ZPSSS5.js.map → chunk-NEXLLTP4.js.map} +1 -1
- package/dist/chunk-QGBJ6GHG.js +2300 -0
- package/dist/chunk-QGBJ6GHG.js.map +1 -0
- package/dist/{chunk-B6WPZSTT.js → chunk-UOLJWT5Y.js} +78 -16
- package/dist/chunk-UOLJWT5Y.js.map +1 -0
- package/dist/{chunk-MKPIAGVL.js → chunk-W6NFJXSA.js} +8 -19
- package/dist/chunk-W6NFJXSA.js.map +1 -0
- package/dist/{chunk-XBZWOM2K.js → chunk-X4A66EWZ.js} +8 -3
- package/dist/chunk-X4A66EWZ.js.map +1 -0
- package/dist/chunk-X4JAFD5R.js +3907 -0
- package/dist/chunk-X4JAFD5R.js.map +1 -0
- package/dist/{chunk-6KLX2RH5.js → chunk-XSA2QAKP.js} +334 -19
- package/dist/chunk-XSA2QAKP.js.map +1 -0
- package/dist/{chunk-HA46CYQK.js → chunk-XXWH2JLC.js} +792 -206
- package/dist/chunk-XXWH2JLC.js.map +1 -0
- package/dist/client.d.ts +2 -2
- package/dist/components/browser-viewer.d.ts +7 -1
- package/dist/components/computer-viewer.d.ts +4 -1
- package/dist/components/enrollment-consent.d.ts +6 -3
- package/dist/components/file-browser.d.ts +7 -1
- package/dist/components/human-input-form.d.ts +1 -1
- package/dist/components/human-input-surface.d.ts +1 -1
- package/dist/components/machine-card.d.ts +4 -1
- package/dist/components/machines/machine-detail.d.ts +4 -1
- package/dist/components/machines/machine-operation-policy.d.ts +11 -0
- package/dist/components/machines-dashboard.d.ts +3 -1
- package/dist/components/markdown.d.ts +20 -1
- package/dist/components/message-timeline.d.ts +1 -1
- package/dist/components/model-policy-picker.d.ts +2 -0
- package/dist/components/pierre-file.d.ts +1 -1
- package/dist/components/sandbox-files.d.ts +8 -2
- package/dist/components/sandbox-workspace.d.ts +33 -3
- package/dist/components/session-chrome.d.ts +15 -3
- package/dist/components/workspace-dock.d.ts +6 -2
- package/dist/composer.js +5 -5
- package/dist/computer-viewer-ZOP4Q7A7.js +16 -0
- package/dist/computer-viewer-ZOP4Q7A7.js.map +1 -0
- package/dist/hooks/channel-cache.d.ts +23 -0
- package/dist/hooks/shared-machines-list.d.ts +14 -0
- package/dist/hooks/use-attached-browsers.d.ts +2 -1
- package/dist/hooks/use-channels.d.ts +1 -0
- package/dist/hooks/use-composer.d.ts +37 -9
- package/dist/hooks/use-machine-chip.d.ts +3 -3
- package/dist/hooks/use-machines.d.ts +19 -1
- package/dist/hooks/use-sandbox-files.d.ts +1 -0
- package/dist/hooks/use-session-background-commands.d.ts +14 -0
- package/dist/hooks/use-turn-queue.d.ts +12 -0
- package/dist/hooks/use-workspace-sessions.d.ts +1 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1096 -623
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +29 -181
- package/dist/interaction.js.map +1 -1
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/machines.d.ts +4 -2
- package/dist/machines.js +10 -2
- package/dist/model-policy.d.ts +1 -1
- package/dist/model-policy.js +1 -1
- package/dist/platform-activity-row-BJHUHXAT.js +169 -0
- package/dist/platform-activity-row-BJHUHXAT.js.map +1 -0
- package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-BSKUWHRI.js} +76 -16
- package/dist/queue-surface-implementation-BSKUWHRI.js.map +1 -0
- package/dist/realtime/realtime-control.d.ts +10 -3
- package/dist/realtime.js +138 -29
- package/dist/realtime.js.map +1 -1
- package/dist/session-ui.d.ts +1 -1
- package/dist/session-ui.js +6 -2
- package/dist/session.d.ts +2 -2
- package/dist/session.js +4 -4
- package/dist/timeline/index.d.ts +1 -1
- package/dist/timeline/platform-activity-row.d.ts +15 -0
- package/dist/timeline/screenshot-lightbox.d.ts +1 -1
- package/dist/timeline/shared.d.ts +4 -2
- package/dist/timeline/types.d.ts +48 -5
- package/dist/types/machines.d.ts +2 -2
- package/package.json +3 -3
- package/src/client.ts +12 -1
- package/src/components/artifacts/editable-spreadsheet.tsx +1 -1
- package/src/components/browser-viewer.tsx +361 -58
- package/src/components/chat-composer.tsx +1 -1
- package/src/components/composer.tsx +6 -6
- package/src/components/computer-viewer.tsx +368 -115
- package/src/components/enrollment-consent.tsx +64 -6
- package/src/components/file-browser.tsx +117 -2
- package/src/components/human-input-form.tsx +91 -55
- package/src/components/human-input-surface.tsx +1 -1
- package/src/components/machine-card.tsx +111 -0
- package/src/components/machine-input-display.ts +15 -0
- package/src/components/machines/machine-detail.tsx +47 -0
- package/src/components/machines/machine-operation-policy.tsx +179 -0
- package/src/components/machines-dashboard.tsx +6 -0
- package/src/components/markdown.tsx +174 -20
- package/src/components/message-timeline.tsx +162 -17
- package/src/components/model-policy-picker.tsx +9 -17
- package/src/components/pierre-file.tsx +1 -1
- package/src/components/queue-surface-implementation.tsx +82 -16
- package/src/components/sandbox-files.tsx +135 -19
- package/src/components/sandbox-workspace.tsx +204 -92
- package/src/components/session-chrome.tsx +415 -208
- package/src/components/session-status.tsx +1 -1
- package/src/components/user-message-body.tsx +168 -48
- package/src/components/workspace-dock.tsx +141 -175
- package/src/hooks/channel-cache.ts +104 -0
- package/src/hooks/shared-machines-list.ts +239 -0
- package/src/hooks/use-attached-browsers.ts +5 -1
- package/src/hooks/use-browser-frame-stream.ts +6 -1
- package/src/hooks/use-browser-session.ts +6 -4
- package/src/hooks/use-browser-sessions.ts +7 -1
- package/src/hooks/use-channels.ts +32 -10
- package/src/hooks/use-composer.ts +760 -291
- package/src/hooks/use-computer-frame-stream.ts +14 -9
- package/src/hooks/use-computer-session.ts +16 -14
- package/src/hooks/use-computer-sessions.ts +8 -1
- package/src/hooks/use-last-started-turn-policy.ts +1 -1
- package/src/hooks/use-machine-chip.ts +23 -6
- package/src/hooks/use-machines.ts +104 -6
- package/src/hooks/use-sandbox-files.ts +195 -140
- package/src/hooks/use-sandbox-git.ts +58 -29
- package/src/hooks/use-session-background-commands.ts +56 -0
- package/src/hooks/use-session-capabilities.ts +18 -0
- package/src/hooks/use-turn-queue.ts +111 -12
- package/src/hooks/use-workspace-sessions.ts +5 -1
- package/src/index.ts +26 -4
- package/src/lib/interaction-errors.ts +18 -0
- package/src/machines.ts +9 -1
- package/src/model-policy.ts +10 -2
- package/src/realtime/realtime-control.tsx +200 -37
- package/src/session-context.ts +1 -0
- package/src/session-ui.ts +6 -1
- package/src/session.ts +9 -1
- package/src/timeline/activity-rail.tsx +20 -75
- package/src/timeline/index.ts +4 -0
- package/src/timeline/platform-activity-row.tsx +201 -0
- package/src/timeline/projection.ts +476 -23
- package/src/timeline/screenshot-lightbox.tsx +48 -14
- package/src/timeline/shared.tsx +8 -2
- package/src/timeline/tool-renderers.tsx +104 -6
- package/src/timeline/turn-summary.tsx +6 -1
- package/src/timeline/types.ts +75 -2
- package/src/types/machines.ts +10 -0
- package/styles/compiled.css +115 -104
- package/dist/chunk-5UQPNLIO.js.map +0 -1
- package/dist/chunk-6KLX2RH5.js.map +0 -1
- package/dist/chunk-B6WPZSTT.js.map +0 -1
- package/dist/chunk-HA46CYQK.js.map +0 -1
- package/dist/chunk-IGC6A2ZI.js.map +0 -1
- package/dist/chunk-LLOLUHVG.js +0 -5937
- package/dist/chunk-LLOLUHVG.js.map +0 -1
- package/dist/chunk-MKPIAGVL.js.map +0 -1
- package/dist/chunk-R3AZOAB5.js.map +0 -1
- package/dist/chunk-XBZWOM2K.js.map +0 -1
- package/dist/chunk-Y7OEDUJ5.js.map +0 -1
- package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
- package/dist/queue-surface-implementation-2KVJTYXH.js.map +0 -1
- /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-GXUICIAG.js.map} +0 -0
|
@@ -3,13 +3,18 @@ import {
|
|
|
3
3
|
isValidElement,
|
|
4
4
|
memo,
|
|
5
5
|
useEffect,
|
|
6
|
+
useMemo,
|
|
6
7
|
useReducer,
|
|
7
8
|
useRef,
|
|
8
9
|
useState,
|
|
9
10
|
type ComponentPropsWithoutRef,
|
|
10
11
|
type ReactNode,
|
|
11
12
|
} from "react";
|
|
12
|
-
import ReactMarkdown, {
|
|
13
|
+
import ReactMarkdown, {
|
|
14
|
+
defaultUrlTransform,
|
|
15
|
+
type Components,
|
|
16
|
+
type UrlTransform,
|
|
17
|
+
} from "react-markdown";
|
|
13
18
|
import remarkGfm from "remark-gfm";
|
|
14
19
|
import { cn } from "../lib/cn";
|
|
15
20
|
import { tableElementToTsv } from "../lib/clipboard";
|
|
@@ -46,11 +51,22 @@ export type MarkdownProps = {
|
|
|
46
51
|
* transition — that blinked the whole document).
|
|
47
52
|
*/
|
|
48
53
|
streaming?: boolean | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Open a session-local file link in the host's Files surface. The path is the
|
|
56
|
+
* exact decoded `sandbox:` payload; `line`, when present, is 1-based.
|
|
57
|
+
*/
|
|
58
|
+
onSandboxFile?: ((path: string, line?: number) => void | Promise<void>) | undefined;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Exact target path plus optional 1-based line from a markdown href. */
|
|
62
|
+
export type SandboxFileLocation = {
|
|
63
|
+
path: string;
|
|
64
|
+
line: number | null;
|
|
49
65
|
};
|
|
50
66
|
|
|
51
67
|
/* --- element renderers (themed to og-* tokens) ------------------------------ */
|
|
52
68
|
|
|
53
|
-
const
|
|
69
|
+
const baseComponents: Components = {
|
|
54
70
|
h1: ({ children, ...props }) => (
|
|
55
71
|
<h1
|
|
56
72
|
className="mt-5 mb-2.5 text-xl font-semibold tracking-tight text-og-fg first:mt-0"
|
|
@@ -95,16 +111,6 @@ const components: Components = {
|
|
|
95
111
|
{children}
|
|
96
112
|
</em>
|
|
97
113
|
),
|
|
98
|
-
a: ({ children, ...props }) => (
|
|
99
|
-
<a
|
|
100
|
-
className="break-words font-medium text-og-accent-strong underline-offset-2 hover:underline"
|
|
101
|
-
target="_blank"
|
|
102
|
-
rel="noreferrer noopener"
|
|
103
|
-
{...props}
|
|
104
|
-
>
|
|
105
|
-
{children}
|
|
106
|
-
</a>
|
|
107
|
-
),
|
|
108
114
|
ul: ({ children, ...props }) => (
|
|
109
115
|
<ul
|
|
110
116
|
className="my-2.5 ml-5 flex list-disc flex-col gap-1 marker:text-og-fg-subtle first:mt-0 last:mb-0"
|
|
@@ -184,15 +190,161 @@ const components: Components = {
|
|
|
184
190
|
{children}
|
|
185
191
|
</td>
|
|
186
192
|
),
|
|
187
|
-
img: ({ alt, ...props }) =>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
img: ({ alt, src, ...props }) =>
|
|
194
|
+
src ? (
|
|
195
|
+
<img
|
|
196
|
+
alt={alt ?? ""}
|
|
197
|
+
src={src}
|
|
198
|
+
className="my-3 max-w-full rounded-og-md border border-og-border"
|
|
199
|
+
{...props}
|
|
200
|
+
/>
|
|
201
|
+
) : alt ? (
|
|
202
|
+
<span className="text-og-fg-subtle" aria-disabled="true">
|
|
203
|
+
{alt}
|
|
204
|
+
</span>
|
|
205
|
+
) : null,
|
|
194
206
|
};
|
|
195
207
|
|
|
208
|
+
const MARKDOWN_LINK_CLASS =
|
|
209
|
+
"break-words font-medium text-og-accent-strong underline-offset-2 hover:underline";
|
|
210
|
+
|
|
211
|
+
function markdownComponents(onSandboxFile: MarkdownProps["onSandboxFile"]): Components {
|
|
212
|
+
return {
|
|
213
|
+
...baseComponents,
|
|
214
|
+
a: ({ children, href, ...props }) => {
|
|
215
|
+
const location = sandboxFileLocationFromHref(href);
|
|
216
|
+
if (location !== null) {
|
|
217
|
+
return (
|
|
218
|
+
<SandboxMarkdownLink location={location} onSandboxFile={onSandboxFile}>
|
|
219
|
+
{children}
|
|
220
|
+
</SandboxMarkdownLink>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (isSandboxHref(href) || !href) {
|
|
224
|
+
return (
|
|
225
|
+
<span
|
|
226
|
+
className="break-words font-medium text-og-fg-subtle underline decoration-dotted underline-offset-2"
|
|
227
|
+
aria-disabled="true"
|
|
228
|
+
title={
|
|
229
|
+
isSandboxHref(href)
|
|
230
|
+
? "This sandbox file reference is invalid"
|
|
231
|
+
: "This link is unavailable"
|
|
232
|
+
}
|
|
233
|
+
>
|
|
234
|
+
{children}
|
|
235
|
+
</span>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
return (
|
|
239
|
+
<a
|
|
240
|
+
className={MARKDOWN_LINK_CLASS}
|
|
241
|
+
target="_blank"
|
|
242
|
+
rel="noreferrer noopener"
|
|
243
|
+
href={href}
|
|
244
|
+
{...props}
|
|
245
|
+
>
|
|
246
|
+
{children}
|
|
247
|
+
</a>
|
|
248
|
+
);
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function SandboxMarkdownLink({
|
|
254
|
+
location,
|
|
255
|
+
onSandboxFile,
|
|
256
|
+
children,
|
|
257
|
+
}: {
|
|
258
|
+
location: SandboxFileLocation;
|
|
259
|
+
onSandboxFile: MarkdownProps["onSandboxFile"];
|
|
260
|
+
children: ReactNode;
|
|
261
|
+
}) {
|
|
262
|
+
const [state, setState] = useState<"idle" | "loading" | "error">("idle");
|
|
263
|
+
const { path, line } = location;
|
|
264
|
+
if (!onSandboxFile) {
|
|
265
|
+
return (
|
|
266
|
+
<span
|
|
267
|
+
className="break-words font-medium text-og-fg-subtle underline decoration-dotted underline-offset-2"
|
|
268
|
+
aria-disabled="true"
|
|
269
|
+
title="This sandbox file requires a session-aware handler"
|
|
270
|
+
>
|
|
271
|
+
{children}
|
|
272
|
+
</span>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
const idleTitle = line !== null ? `Open ${path} at line ${line}` : `Open ${path}`;
|
|
276
|
+
return (
|
|
277
|
+
<button
|
|
278
|
+
type="button"
|
|
279
|
+
className={cn(
|
|
280
|
+
MARKDOWN_LINK_CLASS,
|
|
281
|
+
"inline-flex min-h-7 cursor-pointer items-center disabled:cursor-wait disabled:opacity-70 pointer-coarse:min-h-11",
|
|
282
|
+
)}
|
|
283
|
+
disabled={state === "loading"}
|
|
284
|
+
aria-busy={state === "loading"}
|
|
285
|
+
title={state === "error" ? "Couldn't open this file. Select to retry." : idleTitle}
|
|
286
|
+
onClick={() => {
|
|
287
|
+
setState("loading");
|
|
288
|
+
void Promise.resolve(onSandboxFile(path, line ?? undefined)).then(
|
|
289
|
+
() => setState("idle"),
|
|
290
|
+
() => setState("error"),
|
|
291
|
+
);
|
|
292
|
+
}}
|
|
293
|
+
>
|
|
294
|
+
{children}
|
|
295
|
+
{state === "loading" ? " (opening…)" : state === "error" ? " (retry)" : null}
|
|
296
|
+
</button>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const SANDBOX_SCHEME = "sandbox:";
|
|
301
|
+
const LEGACY_WORKSPACE_PREFIX = "/workspace/";
|
|
302
|
+
const TRAILING_LINE = /:([0-9]+)$/u;
|
|
303
|
+
const TRAILING_INVALID_LINE = /:-(?:[0-9]+)$|:[0-9]+-[0-9]+$/u;
|
|
304
|
+
const CONTROL_CHARACTER = /[\u0000-\u001f\u007f]/u;
|
|
305
|
+
|
|
306
|
+
function isSandboxHref(href: string | undefined): boolean {
|
|
307
|
+
return href?.startsWith(SANDBOX_SCHEME) ?? false;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Parse an OpenGeni `sandbox:` application href (plus the historical bare
|
|
312
|
+
* `/workspace/...` form). The decoded path is intentionally opaque here:
|
|
313
|
+
* target selection, path policy, and filesystem authority belong to the
|
|
314
|
+
* session-aware host and its FileSystem boundary.
|
|
315
|
+
*/
|
|
316
|
+
export function sandboxFileLocationFromHref(href: string | undefined): SandboxFileLocation | null {
|
|
317
|
+
if (!href) return null;
|
|
318
|
+
const encodedLocation = isSandboxHref(href)
|
|
319
|
+
? href.slice(SANDBOX_SCHEME.length)
|
|
320
|
+
: href.startsWith(LEGACY_WORKSPACE_PREFIX)
|
|
321
|
+
? href
|
|
322
|
+
: null;
|
|
323
|
+
if (!encodedLocation || encodedLocation.includes("?") || encodedLocation.includes("#")) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
if (TRAILING_INVALID_LINE.test(encodedLocation)) return null;
|
|
327
|
+
const lineMatch = TRAILING_LINE.exec(encodedLocation);
|
|
328
|
+
const encodedPath = lineMatch ? encodedLocation.slice(0, -lineMatch[0].length) : encodedLocation;
|
|
329
|
+
const line = lineMatch?.[1] ? Number(lineMatch[1]) : null;
|
|
330
|
+
if (line !== null && (!Number.isSafeInteger(line) || line < 1)) return null;
|
|
331
|
+
if (!encodedPath) return null;
|
|
332
|
+
try {
|
|
333
|
+
const path = decodeURIComponent(encodedPath);
|
|
334
|
+
return path && !CONTROL_CHARACTER.test(path) ? { path, line } : null;
|
|
335
|
+
} catch {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Return the exact decoded path for a supported sandbox-link href. */
|
|
341
|
+
export function sandboxFilePathFromHref(href: string | undefined): string | null {
|
|
342
|
+
return sandboxFileLocationFromHref(href)?.path ?? null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const markdownUrlTransform: UrlTransform = (url, key, node) =>
|
|
346
|
+
key === "href" && node.tagName === "a" && isSandboxHref(url) ? url : defaultUrlTransform(url);
|
|
347
|
+
|
|
196
348
|
/** How long after the stream ends the reveal pipeline stays for trailing animations. */
|
|
197
349
|
/** Trailing ink window after stream end — keep ≥ {@link INK_FADE_MS}. */
|
|
198
350
|
const REVEAL_LINGER_MS = 480 * MOTION_INSPECT_SCALE;
|
|
@@ -284,7 +436,7 @@ function MarkdownTable({ children, className, ...props }: ComponentPropsWithoutR
|
|
|
284
436
|
);
|
|
285
437
|
}
|
|
286
438
|
|
|
287
|
-
function MarkdownImpl({ children, className, streaming = false }: MarkdownProps) {
|
|
439
|
+
function MarkdownImpl({ children, className, streaming = false, onSandboxFile }: MarkdownProps) {
|
|
288
440
|
// Tip-ink engine for THIS body: created on the first streaming render, kept
|
|
289
441
|
// through a short linger after the stream ends (so the last age window can
|
|
290
442
|
// finish), then dropped so settled bodies pay zero cost. Observing during
|
|
@@ -373,6 +525,7 @@ function MarkdownImpl({ children, className, streaming = false }: MarkdownProps)
|
|
|
373
525
|
// fences don't snap to final GFM one commit before the crystallize morph.
|
|
374
526
|
// Reveal identity still tracks the true source (`children`).
|
|
375
527
|
const parseText = streaming || revealActive ? softenStreamingMarkdown(children) : children;
|
|
528
|
+
const components = useMemo(() => markdownComponents(onSandboxFile), [onSandboxFile]);
|
|
376
529
|
|
|
377
530
|
// `min-w-0` lets the prose shrink inside flex parents (message bubbles) so
|
|
378
531
|
// long links and code blocks wrap/scroll instead of forcing overflow.
|
|
@@ -390,6 +543,7 @@ function MarkdownImpl({ children, className, streaming = false }: MarkdownProps)
|
|
|
390
543
|
remarkPlugins={[remarkGfm]}
|
|
391
544
|
rehypePlugins={rehypePlugins}
|
|
392
545
|
components={components}
|
|
546
|
+
urlTransform={markdownUrlTransform}
|
|
393
547
|
>
|
|
394
548
|
{parseText}
|
|
395
549
|
</ReactMarkdown>
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
ChevronRightIcon,
|
|
15
15
|
PauseCircleIcon,
|
|
16
16
|
PauseIcon,
|
|
17
|
+
MessageCircleQuestionIcon,
|
|
17
18
|
PencilLineIcon,
|
|
18
19
|
PlayIcon,
|
|
19
20
|
RefreshCwIcon,
|
|
@@ -71,6 +72,7 @@ import {
|
|
|
71
72
|
type AuthNeededItem,
|
|
72
73
|
type ContextCompactionItem,
|
|
73
74
|
type GoalItem,
|
|
75
|
+
type HumanInputItem,
|
|
74
76
|
type MachineInputBatchItem,
|
|
75
77
|
type NoticeItem,
|
|
76
78
|
type TimelineGroup,
|
|
@@ -160,7 +162,7 @@ export type MessageTimelineProps = {
|
|
|
160
162
|
toolRegistry?: ToolRegistry | undefined;
|
|
161
163
|
/** Resolve opaque retained screenshot receipts through the authenticated host SDK. */
|
|
162
164
|
loadRetainedScreenshot?: RetainedScreenshotLoader | undefined;
|
|
163
|
-
/** Resolve permanent
|
|
165
|
+
/** Resolve permanent workspace image/file receipts through the authenticated host SDK. */
|
|
164
166
|
loadRetainedArtifact?: RetainedArtifactLoader | undefined;
|
|
165
167
|
/** Mint short-lived native playback sources for retained generated videos. */
|
|
166
168
|
loadVideoArtifactPlayback?: VideoArtifactPlaybackLoader | undefined;
|
|
@@ -375,7 +377,9 @@ export function MessageTimeline({
|
|
|
375
377
|
// post-commit settle (two rAFs). That absorbs sync late layout while hidden
|
|
376
378
|
// so load/remount does not ease into the tip — live tip-follow is unchanged
|
|
377
379
|
// once revealed. A flash of the window's TOP is still structurally impossible.
|
|
378
|
-
|
|
380
|
+
// The accepted-create handoff uses the reserved local id "c" so its known
|
|
381
|
+
// first message is visible immediately; loaded histories still park first.
|
|
382
|
+
const [revealed, setRevealed] = useState(resolvedItems[0]?.id === "c");
|
|
379
383
|
// Mirror `pinned` into a ref, written ONLY by applyPinned, so the
|
|
380
384
|
// ResizeObserver rAF (a stable closure) reads the live value and a snap can
|
|
381
385
|
// never race a just-unpinned reader across a pending React commit.
|
|
@@ -2031,7 +2035,9 @@ function useLiveSettleFold(folded: boolean): boolean {
|
|
|
2031
2035
|
function timelineGroupKey(group: TimelineGroup): string {
|
|
2032
2036
|
switch (group.kind) {
|
|
2033
2037
|
case "item":
|
|
2034
|
-
return group.item.
|
|
2038
|
+
return group.item.kind === "user-message" && group.item.reconciliationKey
|
|
2039
|
+
? group.item.reconciliationKey
|
|
2040
|
+
: group.item.id;
|
|
2035
2041
|
case "activity":
|
|
2036
2042
|
return group.id;
|
|
2037
2043
|
case "turn":
|
|
@@ -2249,6 +2255,8 @@ export function TimelineRow({
|
|
|
2249
2255
|
switch (item.kind) {
|
|
2250
2256
|
case "user-message":
|
|
2251
2257
|
return <UserMessageRow item={item} renderMessageText={renderMessageText} />;
|
|
2258
|
+
case "human-input":
|
|
2259
|
+
return <HumanInputConversationRow item={item} />;
|
|
2252
2260
|
case "agent-message":
|
|
2253
2261
|
return <AgentMessageRow item={item} renderMessageText={renderMessageText} />;
|
|
2254
2262
|
case "worker-completion":
|
|
@@ -2380,14 +2388,7 @@ function UserMessageRow({
|
|
|
2380
2388
|
| undefined;
|
|
2381
2389
|
}) {
|
|
2382
2390
|
const enter = useEntranceAnimation();
|
|
2383
|
-
const
|
|
2384
|
-
item.delivery?.state === "sending"
|
|
2385
|
-
? "Sending…"
|
|
2386
|
-
: item.delivery?.state === "queued"
|
|
2387
|
-
? "Queued"
|
|
2388
|
-
: item.delivery?.state === "failed"
|
|
2389
|
-
? "Not sent"
|
|
2390
|
-
: null;
|
|
2391
|
+
const deliveryFailed = item.delivery?.state === "failed";
|
|
2391
2392
|
return (
|
|
2392
2393
|
<div className={cn(enter && "animate-og-enter", "flex justify-end")}>
|
|
2393
2394
|
<div className="flex max-w-[85%] min-w-0 flex-col items-end gap-1">
|
|
@@ -2422,19 +2423,25 @@ function UserMessageRow({
|
|
|
2422
2423
|
) : null}
|
|
2423
2424
|
</div>
|
|
2424
2425
|
</CopyHoverFrame>
|
|
2425
|
-
{
|
|
2426
|
-
<div
|
|
2427
|
-
|
|
2428
|
-
|
|
2426
|
+
{deliveryFailed ? (
|
|
2427
|
+
<div
|
|
2428
|
+
role="status"
|
|
2429
|
+
className="flex max-w-full items-center gap-2 px-1 text-og-xs text-og-status-failed"
|
|
2430
|
+
>
|
|
2431
|
+
<span className="inline-flex items-center gap-1" title={item.delivery?.error}>
|
|
2432
|
+
<TriangleAlertIcon className="size-3.5 shrink-0" aria-hidden="true" />
|
|
2433
|
+
<span>Message not sent</span>
|
|
2434
|
+
</span>
|
|
2435
|
+
{item.delivery?.onRetry ? (
|
|
2429
2436
|
<button
|
|
2430
2437
|
type="button"
|
|
2431
|
-
className="font-medium text-og-
|
|
2438
|
+
className="font-medium text-og-status-failed underline decoration-og-status-failed/50 underline-offset-2 hover:text-og-fg"
|
|
2432
2439
|
onClick={item.delivery.onRetry}
|
|
2433
2440
|
>
|
|
2434
2441
|
Retry
|
|
2435
2442
|
</button>
|
|
2436
2443
|
) : null}
|
|
2437
|
-
{item.delivery?.
|
|
2444
|
+
{item.delivery?.onRemove ? (
|
|
2438
2445
|
<button
|
|
2439
2446
|
type="button"
|
|
2440
2447
|
className="font-medium text-og-fg-subtle underline decoration-og-border underline-offset-2 hover:text-og-fg"
|
|
@@ -2450,6 +2457,139 @@ function UserMessageRow({
|
|
|
2450
2457
|
);
|
|
2451
2458
|
}
|
|
2452
2459
|
|
|
2460
|
+
function HumanInputConversationRow({ item }: { item: HumanInputItem }) {
|
|
2461
|
+
const enter = useEntranceAnimation();
|
|
2462
|
+
const multipleQuestions = Math.max(item.questions.length, item.answers.length) > 1;
|
|
2463
|
+
const questionNumberById = new Map(
|
|
2464
|
+
item.questions.map((question, index) => [question.id, index + 1]),
|
|
2465
|
+
);
|
|
2466
|
+
const settledLabel =
|
|
2467
|
+
item.response.outcome === "answered"
|
|
2468
|
+
? "You answered"
|
|
2469
|
+
: item.response.outcome === "skipped"
|
|
2470
|
+
? "Skipped"
|
|
2471
|
+
: item.response.outcome === "expired"
|
|
2472
|
+
? "Expired"
|
|
2473
|
+
: "Cancelled";
|
|
2474
|
+
const copyText = humanInputConversationCopyText(item, settledLabel);
|
|
2475
|
+
|
|
2476
|
+
return (
|
|
2477
|
+
<div
|
|
2478
|
+
className={cn(enter && "animate-og-enter", "flex w-full flex-col gap-2.5")}
|
|
2479
|
+
data-human-input-history={item.requestId}
|
|
2480
|
+
>
|
|
2481
|
+
<div className="flex max-w-[90%] items-start gap-3 rounded-og-lg rounded-bl-og-xs border border-og-border bg-og-surface-1 px-3.5 py-3 sm:max-w-[82%]">
|
|
2482
|
+
<span className="mt-0.5 inline-flex size-8 shrink-0 items-center justify-center rounded-og-md bg-og-status-waiting/10 text-og-status-waiting">
|
|
2483
|
+
<MessageCircleQuestionIcon aria-hidden="true" className="size-4" />
|
|
2484
|
+
</span>
|
|
2485
|
+
<div className="min-w-0 flex-1">
|
|
2486
|
+
<p className="text-og-xs font-medium text-og-fg-subtle">Agent asked</p>
|
|
2487
|
+
<div className="mt-1.5 space-y-3">
|
|
2488
|
+
{item.questions.length > 0 ? (
|
|
2489
|
+
item.questions.map((question, index) => (
|
|
2490
|
+
<div key={question.id}>
|
|
2491
|
+
{question.label ? (
|
|
2492
|
+
<p className="text-og-sm font-semibold text-og-fg">
|
|
2493
|
+
{multipleQuestions ? (
|
|
2494
|
+
<span className="mr-1.5 tabular-nums text-og-fg-muted">{index + 1}.</span>
|
|
2495
|
+
) : null}
|
|
2496
|
+
{multipleQuestions ? " " : null}
|
|
2497
|
+
{question.label}
|
|
2498
|
+
</p>
|
|
2499
|
+
) : null}
|
|
2500
|
+
<p
|
|
2501
|
+
className={cn(
|
|
2502
|
+
"text-og-md leading-6 text-og-fg",
|
|
2503
|
+
question.label && "mt-0.5 text-og-sm text-og-fg-muted",
|
|
2504
|
+
)}
|
|
2505
|
+
>
|
|
2506
|
+
{!question.label && multipleQuestions ? (
|
|
2507
|
+
<span className="mr-1.5 tabular-nums text-og-fg-muted">{index + 1}.</span>
|
|
2508
|
+
) : null}
|
|
2509
|
+
{!question.label && multipleQuestions ? " " : null}
|
|
2510
|
+
{question.prompt}
|
|
2511
|
+
</p>
|
|
2512
|
+
</div>
|
|
2513
|
+
))
|
|
2514
|
+
) : (
|
|
2515
|
+
<p className="text-og-sm text-og-fg-muted">The agent requested structured input.</p>
|
|
2516
|
+
)}
|
|
2517
|
+
</div>
|
|
2518
|
+
</div>
|
|
2519
|
+
</div>
|
|
2520
|
+
|
|
2521
|
+
<div className="flex justify-end">
|
|
2522
|
+
<CopyHoverFrame
|
|
2523
|
+
copyText={copyText}
|
|
2524
|
+
label="Copy answer"
|
|
2525
|
+
className="w-fit max-w-[90%] min-w-0 sm:max-w-[82%]"
|
|
2526
|
+
trailing={<MessageFooterTime occurredAt={item.occurredAt} />}
|
|
2527
|
+
>
|
|
2528
|
+
<div className="w-fit max-w-full min-w-0 rounded-og-lg rounded-br-og-xs border border-og-border bg-og-surface-2 px-4 py-2.5 text-og-md leading-6 text-og-fg">
|
|
2529
|
+
<p className="text-og-xs font-medium text-og-fg-subtle">{settledLabel}</p>
|
|
2530
|
+
{item.response.outcome === "answered" ? (
|
|
2531
|
+
<div className="mt-1.5 space-y-3">
|
|
2532
|
+
{item.answers.length > 0 ? (
|
|
2533
|
+
item.answers.map((answer, answerIndex) => (
|
|
2534
|
+
<div key={answer.questionId}>
|
|
2535
|
+
{multipleQuestions ? (
|
|
2536
|
+
<p className="text-og-sm font-semibold text-og-fg">
|
|
2537
|
+
<span className="mr-1.5 tabular-nums text-og-fg-muted">
|
|
2538
|
+
{questionNumberById.get(answer.questionId) ?? answerIndex + 1}.
|
|
2539
|
+
</span>{" "}
|
|
2540
|
+
{answer.label}
|
|
2541
|
+
</p>
|
|
2542
|
+
) : null}
|
|
2543
|
+
<div className={cn("text-og-md text-og-fg", multipleQuestions && "mt-0.5")}>
|
|
2544
|
+
{answer.values.length > 1 ? (
|
|
2545
|
+
<ul className="list-disc space-y-0.5 pl-5">
|
|
2546
|
+
{answer.values.map((value) => (
|
|
2547
|
+
<li key={`${answer.questionId}-${value}`}>{value}</li>
|
|
2548
|
+
))}
|
|
2549
|
+
</ul>
|
|
2550
|
+
) : (
|
|
2551
|
+
<p>{answer.values[0] || "Answered"}</p>
|
|
2552
|
+
)}
|
|
2553
|
+
</div>
|
|
2554
|
+
</div>
|
|
2555
|
+
))
|
|
2556
|
+
) : (
|
|
2557
|
+
<p>Answered</p>
|
|
2558
|
+
)}
|
|
2559
|
+
</div>
|
|
2560
|
+
) : null}
|
|
2561
|
+
</div>
|
|
2562
|
+
</CopyHoverFrame>
|
|
2563
|
+
</div>
|
|
2564
|
+
</div>
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
function humanInputConversationCopyText(item: HumanInputItem, settledLabel: string): string {
|
|
2569
|
+
const questions = item.questions
|
|
2570
|
+
.map(
|
|
2571
|
+
(question, index) =>
|
|
2572
|
+
`${item.questions.length > 1 ? `${index + 1}. ` : ""}${question.label || "Question"}: ${question.prompt}`,
|
|
2573
|
+
)
|
|
2574
|
+
.join("\n\n");
|
|
2575
|
+
const questionNumberById = new Map(
|
|
2576
|
+
item.questions.map((question, index) => [question.id, index + 1]),
|
|
2577
|
+
);
|
|
2578
|
+
const answers = item.answers
|
|
2579
|
+
.map(
|
|
2580
|
+
(answer, index) =>
|
|
2581
|
+
`${
|
|
2582
|
+
item.questions.length > 1
|
|
2583
|
+
? `${questionNumberById.get(answer.questionId) ?? index + 1}. `
|
|
2584
|
+
: ""
|
|
2585
|
+
}${answer.label}: ${answer.values.join(", ") || "Answered"}`,
|
|
2586
|
+
)
|
|
2587
|
+
.join("\n\n");
|
|
2588
|
+
return [questions, `${settledLabel}${answers ? `\n\n${answers}` : ""}`]
|
|
2589
|
+
.filter(Boolean)
|
|
2590
|
+
.join("\n\n");
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2453
2593
|
function AgentMessageRow({
|
|
2454
2594
|
item,
|
|
2455
2595
|
renderMessageText,
|
|
@@ -2701,6 +2841,11 @@ const GOAL_META: Record<GoalItem["action"], GoalMeta> = {
|
|
|
2701
2841
|
},
|
|
2702
2842
|
resumed: { label: "Goal resumed", pill: NEUTRAL_PILL, icon: PlayIcon },
|
|
2703
2843
|
cleared: { label: "Goal cleared", pill: NEUTRAL_PILL, icon: Trash2Icon },
|
|
2844
|
+
held: {
|
|
2845
|
+
label: "Goal held",
|
|
2846
|
+
pill: "border-og-status-waiting/35 bg-og-status-waiting/10 text-og-status-waiting",
|
|
2847
|
+
icon: PauseCircleIcon,
|
|
2848
|
+
},
|
|
2704
2849
|
continuation: { label: "Continuing toward the goal", pill: NEUTRAL_PILL, icon: ArrowRightIcon },
|
|
2705
2850
|
};
|
|
2706
2851
|
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ChevronDownIcon,
|
|
5
5
|
ChevronLeftIcon,
|
|
6
6
|
ChevronRightIcon,
|
|
7
|
+
Globe2Icon,
|
|
7
8
|
KeyRoundIcon,
|
|
8
9
|
ZapIcon,
|
|
9
10
|
} from "lucide-react";
|
|
@@ -20,7 +21,6 @@ import {
|
|
|
20
21
|
import { cn } from "../lib/cn";
|
|
21
22
|
import { usePortalTokenSource, usePortalTokenStyle } from "../lib/use-portal-token-style";
|
|
22
23
|
import {
|
|
23
|
-
coerceReasoningEffortForModel,
|
|
24
24
|
effortOptionsForModel,
|
|
25
25
|
findPickerRow,
|
|
26
26
|
groupPickerRowsByBillingClass,
|
|
@@ -64,6 +64,7 @@ export const defaultModelPolicyPickerMessages: ModelPolicyPickerMessages = {
|
|
|
64
64
|
codexOnly: "Codex-only session",
|
|
65
65
|
billingHints: {
|
|
66
66
|
opengeni_credits: "Will use credits",
|
|
67
|
+
external: "Provider terms and limits apply",
|
|
67
68
|
codex_subscription: "ChatGPT / Codex plan",
|
|
68
69
|
supergrok_subscription: "SuperGrok / xAI plan",
|
|
69
70
|
byok: "Billed to your AI Gateway",
|
|
@@ -92,6 +93,8 @@ export type ModelPolicyPickerProps = {
|
|
|
92
93
|
sessionKey?: string | undefined;
|
|
93
94
|
/** Prefer bottom on new-chat surfaces and top for bottom-docked composers. */
|
|
94
95
|
menuSide?: "top" | "bottom" | undefined;
|
|
96
|
+
/** Hide latency controls on surfaces whose saved policy does not include latency. */
|
|
97
|
+
allowLatencyMode?: boolean | undefined;
|
|
95
98
|
/** Classes for the portalled menu surface. Prefer --og-* tokens for theming. */
|
|
96
99
|
contentClassName?: string | undefined;
|
|
97
100
|
/** Inline styles for the portalled menu, applied after inherited --og-* tokens. */
|
|
@@ -151,6 +154,7 @@ export function BillingClassMark(props: {
|
|
|
151
154
|
}) {
|
|
152
155
|
const labels: Record<PickerBillingClass, string> = {
|
|
153
156
|
opengeni_credits: "OpenGeni",
|
|
157
|
+
external: "External provider",
|
|
154
158
|
codex_subscription: "Codex",
|
|
155
159
|
supergrok_subscription: "SuperGrok",
|
|
156
160
|
byok: "Bring your own key",
|
|
@@ -173,6 +177,8 @@ export function BillingClassMark(props: {
|
|
|
173
177
|
>
|
|
174
178
|
{props.billingClass === "opengeni_credits" ? (
|
|
175
179
|
<OpenGeniMark className={mark} />
|
|
180
|
+
) : props.billingClass === "external" ? (
|
|
181
|
+
<Globe2Icon className={mark} aria-hidden />
|
|
176
182
|
) : props.billingClass === "codex_subscription" ? (
|
|
177
183
|
<ChatGptMark className={mark} />
|
|
178
184
|
) : props.billingClass === "supergrok_subscription" ? (
|
|
@@ -354,17 +360,6 @@ export function ModelPolicyPickerMenu(
|
|
|
354
360
|
const focusModel =
|
|
355
361
|
effectiveNav.modelId === null ? undefined : findPickerRow(rows, effectiveNav.modelId);
|
|
356
362
|
const selectedRow = findPickerRow(rows, props.model);
|
|
357
|
-
const { latencyMode, loading, onLatencyModeChange } = props;
|
|
358
|
-
|
|
359
|
-
useEffect(() => {
|
|
360
|
-
if (loading || !selectedRow?.selectable) return;
|
|
361
|
-
const supportsCurrentMode = runnableLatencyModesForModel(selectedRow.catalog).includes(
|
|
362
|
-
latencyMode,
|
|
363
|
-
);
|
|
364
|
-
if (!supportsCurrentMode && latencyMode !== "standard") {
|
|
365
|
-
onLatencyModeChange("standard");
|
|
366
|
-
}
|
|
367
|
-
}, [latencyMode, loading, onLatencyModeChange, selectedRow]);
|
|
368
363
|
|
|
369
364
|
const go = (next: PickerNavState, nextDirection: 1 | -1) => {
|
|
370
365
|
setDirection(nextDirection);
|
|
@@ -374,7 +369,7 @@ export function ModelPolicyPickerMenu(
|
|
|
374
369
|
const selectEffort = (row: ClientPickerModelRow, effort: ReasoningEffort) => {
|
|
375
370
|
if (!row.selectable) return;
|
|
376
371
|
if (row.id !== props.model) props.onModelChange(row.id);
|
|
377
|
-
props.onEffortChange(
|
|
372
|
+
props.onEffortChange(effort);
|
|
378
373
|
};
|
|
379
374
|
|
|
380
375
|
const pageKey =
|
|
@@ -445,7 +440,7 @@ export function ModelPolicyPickerMenu(
|
|
|
445
440
|
icon={<BillingClassMark billingClass={focusModel.billingClass} />}
|
|
446
441
|
onBack={() => go({ level: "models", rail: focusModel.billingClass, modelId: null }, -1)}
|
|
447
442
|
trailing={
|
|
448
|
-
supportsFast ? (
|
|
443
|
+
supportsFast && props.allowLatencyMode !== false ? (
|
|
449
444
|
<button
|
|
450
445
|
type="button"
|
|
451
446
|
disabled={!focusModel.selectable}
|
|
@@ -460,9 +455,6 @@ export function ModelPolicyPickerMenu(
|
|
|
460
455
|
onClick={() => {
|
|
461
456
|
if (!activeModel) {
|
|
462
457
|
props.onModelChange(focusModel.id);
|
|
463
|
-
props.onEffortChange(
|
|
464
|
-
coerceReasoningEffortForModel(focusModel.catalog, props.effort),
|
|
465
|
-
);
|
|
466
458
|
}
|
|
467
459
|
props.onLatencyModeChange(
|
|
468
460
|
activeModel && props.latencyMode === "fast" ? "standard" : "fast",
|
|
@@ -25,7 +25,7 @@ type FileComponent = ComponentType<{
|
|
|
25
25
|
}>;
|
|
26
26
|
|
|
27
27
|
export type PierreFileProps = {
|
|
28
|
-
/**
|
|
28
|
+
/** Target path (used for the header + language inference). */
|
|
29
29
|
path: string;
|
|
30
30
|
/** The decoded text contents. */
|
|
31
31
|
contents: string;
|