@opengeni/react 5.1.0-canary.0 → 5.2.0-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.
- package/README.md +12 -0
- package/dist/artifacts.d.ts +2 -0
- package/dist/artifacts.js +1391 -2
- package/dist/artifacts.js.map +1 -1
- package/dist/chunk-2TKI3WEG.js +73 -0
- package/dist/chunk-2TKI3WEG.js.map +1 -0
- package/dist/{chunk-M2HNWZYR.js → chunk-IHQII5WO.js} +69 -18
- package/dist/chunk-IHQII5WO.js.map +1 -0
- package/dist/{chunk-5PQJOCX5.js → chunk-KANFU34B.js} +6 -4
- package/dist/chunk-KANFU34B.js.map +1 -0
- package/dist/{chunk-WS6D5BHH.js → chunk-O4NQNMPG.js} +2393 -2123
- package/dist/chunk-O4NQNMPG.js.map +1 -0
- package/dist/{chunk-HUMIRRYI.js → chunk-PZ27MCZP.js} +54 -19
- package/dist/chunk-PZ27MCZP.js.map +1 -0
- package/dist/{chunk-BRFMGY4L.js → chunk-UGMKPUEM.js} +28 -3
- package/dist/chunk-UGMKPUEM.js.map +1 -0
- package/dist/{chunk-BV5A4OTE.js → chunk-YS24CFDI.js} +44 -10
- package/dist/chunk-YS24CFDI.js.map +1 -0
- package/dist/components/artifacts/inline-html-document.d.ts +2 -0
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +2 -0
- package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
- package/dist/components/markdown.d.ts +13 -1
- package/dist/components/session-chrome.d.ts +6 -1
- package/dist/{computer-viewer-7TEZC6Y2.js → computer-viewer-CTFCJVS7.js} +2 -2
- package/dist/connect.js +1 -1
- package/dist/hooks/use-file-attachments.d.ts +2 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +16 -5
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +1 -1
- package/dist/{platform-activity-row-27LKAWZD.js → platform-activity-row-DUE2W74J.js} +6 -3
- package/dist/platform-activity-row-DUE2W74J.js.map +1 -0
- package/dist/session-ui.d.ts +2 -0
- package/dist/session-ui.js +7 -107
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +2 -2
- package/dist/sites-ui.d.ts +2 -1
- package/dist/sites.js +12 -4
- package/dist/sites.js.map +1 -1
- package/dist/timeline/activity-rail.d.ts +1 -0
- package/dist/timeline/genie-loading.d.ts +9 -1
- package/dist/timeline/index.d.ts +3 -1
- package/dist/timeline/knowledge-receipt.d.ts +17 -0
- package/dist/timeline/platform-activity-row.d.ts +1 -2
- package/dist/timeline/retained-image.d.ts +16 -0
- package/dist/timeline/rolling-activity.d.ts +9 -0
- package/dist/timeline/shared.d.ts +4 -1
- package/dist/timeline/turn-summary.d.ts +4 -1
- package/dist/timeline/types.d.ts +13 -2
- package/package.json +4 -4
- package/src/artifacts.ts +2 -0
- package/src/components/artifacts/inline-html-document.ts +24 -0
- package/src/components/artifacts/published-html-artifact-frame.tsx +32 -2
- package/src/components/artifacts/visualization-assets/base.css +1015 -0
- package/src/components/artifacts/visualization-assets/helpers.html +349 -0
- package/src/components/artifacts/visualization-assets.gen.ts +3 -0
- package/src/components/computer-viewer.tsx +11 -3
- package/src/components/copy-button.tsx +7 -2
- package/src/components/markdown.tsx +111 -34
- package/src/components/message-timeline.tsx +48 -3
- package/src/components/session-chrome.tsx +14 -2
- package/src/hooks/use-computer-frame-stream.ts +17 -5
- package/src/hooks/use-computer-session.ts +36 -1
- package/src/hooks/use-file-attachments.ts +4 -1
- package/src/hooks/use-goal.ts +3 -1
- package/src/index.ts +12 -1
- package/src/session-ui.ts +3 -0
- package/src/sites-ui.tsx +23 -6
- package/src/timeline/activity-rail.tsx +31 -4
- package/src/timeline/genie-loading.tsx +18 -4
- package/src/timeline/index.ts +4 -0
- package/src/timeline/knowledge-receipt.tsx +100 -0
- package/src/timeline/platform-activity-row.tsx +5 -3
- package/src/timeline/projection.ts +40 -0
- package/src/timeline/retained-image.ts +91 -0
- package/src/timeline/rolling-activity.tsx +83 -0
- package/src/timeline/shared.tsx +33 -12
- package/src/timeline/tool-renderers.tsx +55 -91
- package/src/timeline/turn-summary.tsx +21 -15
- package/src/timeline/types.ts +14 -0
- package/styles/compiled.css +122 -0
- package/styles/index.css +49 -0
- package/dist/chunk-5PQJOCX5.js.map +0 -1
- package/dist/chunk-BRFMGY4L.js.map +0 -1
- package/dist/chunk-BV5A4OTE.js.map +0 -1
- package/dist/chunk-HUMIRRYI.js.map +0 -1
- package/dist/chunk-M2HNWZYR.js.map +0 -1
- package/dist/chunk-WS6D5BHH.js.map +0 -1
- package/dist/platform-activity-row-27LKAWZD.js.map +0 -1
- /package/dist/{computer-viewer-7TEZC6Y2.js.map → computer-viewer-CTFCJVS7.js.map} +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Children,
|
|
3
|
+
createContext,
|
|
4
|
+
useContext,
|
|
3
5
|
isValidElement,
|
|
4
6
|
memo,
|
|
5
7
|
useEffect,
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
import ReactMarkdown, {
|
|
14
16
|
defaultUrlTransform,
|
|
15
17
|
type Components,
|
|
18
|
+
type ExtraProps,
|
|
16
19
|
type UrlTransform,
|
|
17
20
|
} from "react-markdown";
|
|
18
21
|
import remarkGfm from "remark-gfm";
|
|
@@ -40,7 +43,17 @@ import { TooltipProvider } from "./tooltip";
|
|
|
40
43
|
* the tokens land. Consumers who want a different renderer can still pass
|
|
41
44
|
* `renderMessageText` to `MessageTimeline` to override this entirely.
|
|
42
45
|
*/
|
|
46
|
+
export type MarkdownInteractiveBlock = { kind: "html" | "site"; content: string };
|
|
47
|
+
const InteractiveContext = createContext<{
|
|
48
|
+
source: string;
|
|
49
|
+
render?: (block: MarkdownInteractiveBlock) => ReactNode;
|
|
50
|
+
renderImage?: (image: { src: string; alt: string }) => ReactNode;
|
|
51
|
+
}>({ source: "" });
|
|
52
|
+
|
|
43
53
|
export type MarkdownProps = {
|
|
54
|
+
renderImage?: ((image: { src: string; alt: string }) => ReactNode) | undefined;
|
|
55
|
+
/** Host opt-in for assistant-authored interactive fences. */
|
|
56
|
+
renderInteractiveBlock?: ((block: MarkdownInteractiveBlock) => ReactNode) | undefined;
|
|
44
57
|
children: string;
|
|
45
58
|
className?: string | undefined;
|
|
46
59
|
/**
|
|
@@ -171,7 +184,7 @@ const baseComponents: Components = {
|
|
|
171
184
|
</code>
|
|
172
185
|
),
|
|
173
186
|
// Fenced code — quiet mono block (no card / no nested vertical scroll).
|
|
174
|
-
pre:
|
|
187
|
+
pre: InteractiveCodeBlock,
|
|
175
188
|
// Tables stay unboxed (hairline rules); hover reveals a TSV copy control.
|
|
176
189
|
table: ({ children, ...props }) => <MarkdownTable {...props}>{children}</MarkdownTable>,
|
|
177
190
|
thead: ({ children, ...props }) => <thead {...props}>{children}</thead>,
|
|
@@ -191,19 +204,7 @@ const baseComponents: Components = {
|
|
|
191
204
|
{children}
|
|
192
205
|
</td>
|
|
193
206
|
),
|
|
194
|
-
img:
|
|
195
|
-
src ? (
|
|
196
|
-
<img
|
|
197
|
-
alt={alt ?? ""}
|
|
198
|
-
src={src}
|
|
199
|
-
className="my-3 max-w-full rounded-og-md border border-og-border"
|
|
200
|
-
{...props}
|
|
201
|
-
/>
|
|
202
|
-
) : alt ? (
|
|
203
|
-
<span className="text-og-fg-subtle" aria-disabled="true">
|
|
204
|
-
{alt}
|
|
205
|
-
</span>
|
|
206
|
-
) : null,
|
|
207
|
+
img: MarkdownImage,
|
|
207
208
|
};
|
|
208
209
|
|
|
209
210
|
const MARKDOWN_LINK_CLASS =
|
|
@@ -343,8 +344,30 @@ export function sandboxFilePathFromHref(href: string | undefined): string | null
|
|
|
343
344
|
return sandboxFileLocationFromHref(href)?.path ?? null;
|
|
344
345
|
}
|
|
345
346
|
|
|
347
|
+
export function retainedImageId(src: string): string | null {
|
|
348
|
+
return (
|
|
349
|
+
/^artifact:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i
|
|
350
|
+
.exec(src)?.[1]
|
|
351
|
+
?.toLowerCase() ?? null
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
function MarkdownImage({ src, alt }: ComponentPropsWithoutRef<"img">) {
|
|
355
|
+
const { renderImage } = useContext(InteractiveContext);
|
|
356
|
+
if (!src) return <span>{alt ?? "Image unavailable"}</span>;
|
|
357
|
+
if (retainedImageId(src))
|
|
358
|
+
return renderImage ? (
|
|
359
|
+
<>{renderImage({ src, alt: alt ?? "" })}</>
|
|
360
|
+
) : (
|
|
361
|
+
<span>{alt || "Image"} (preview unavailable)</span>
|
|
362
|
+
);
|
|
363
|
+
return <img src={src} alt={alt ?? ""} loading="lazy" className="my-3 max-w-full rounded-og-md" />;
|
|
364
|
+
}
|
|
365
|
+
|
|
346
366
|
const markdownUrlTransform: UrlTransform = (url, key, node) =>
|
|
347
|
-
key === "href" && node.tagName === "a" && isSandboxHref(url)
|
|
367
|
+
(key === "href" && node.tagName === "a" && isSandboxHref(url)) ||
|
|
368
|
+
(key === "src" && node.tagName === "img" && retainedImageId(url))
|
|
369
|
+
? url
|
|
370
|
+
: defaultUrlTransform(url);
|
|
348
371
|
|
|
349
372
|
/** How long after the stream ends the reveal pipeline stays for trailing animations. */
|
|
350
373
|
/** Trailing ink window after stream end — keep ≥ {@link INK_FADE_MS}. */
|
|
@@ -382,6 +405,42 @@ function fenceLanguage(children: ReactNode): string | null {
|
|
|
382
405
|
return found;
|
|
383
406
|
}
|
|
384
407
|
|
|
408
|
+
function InteractiveCodeBlock({ children, node }: ComponentPropsWithoutRef<"pre"> & ExtraProps) {
|
|
409
|
+
const { source, render } = useContext(InteractiveContext);
|
|
410
|
+
const language = fenceLanguage(children);
|
|
411
|
+
if (render && (language === "opengeni-html" || language === "opengeni-site")) {
|
|
412
|
+
const start = node?.position?.start.offset;
|
|
413
|
+
const end = node?.position?.end.offset;
|
|
414
|
+
const original =
|
|
415
|
+
start !== undefined && end !== undefined && end <= source.length
|
|
416
|
+
? source.slice(start, end)
|
|
417
|
+
: "";
|
|
418
|
+
const lines = original.trimEnd().split("\n");
|
|
419
|
+
const opening = lines[0]?.match(/^ {0,3}([\x60]{3,}|~{3,})/);
|
|
420
|
+
|
|
421
|
+
const closing = lines
|
|
422
|
+
.at(-1)
|
|
423
|
+
?.replace(/^\s*(?:>\s*)+/, "")
|
|
424
|
+
.trim();
|
|
425
|
+
const complete =
|
|
426
|
+
opening &&
|
|
427
|
+
lines.length > 1 &&
|
|
428
|
+
closing &&
|
|
429
|
+
closing.length >= opening[1]!.length &&
|
|
430
|
+
[...closing].every((c) => c === opening[1]![0]);
|
|
431
|
+
if (!complete) return <p role="status">Preparing preview…</p>;
|
|
432
|
+
return (
|
|
433
|
+
<>
|
|
434
|
+
{render({
|
|
435
|
+
kind: language === "opengeni-html" ? "html" : "site",
|
|
436
|
+
content: nodeText(children).replace(/\n$/, ""),
|
|
437
|
+
})}
|
|
438
|
+
</>
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
return <MarkdownCodeBlock>{children}</MarkdownCodeBlock>;
|
|
442
|
+
}
|
|
443
|
+
|
|
385
444
|
function MarkdownCodeBlock({ children }: { children?: ReactNode }) {
|
|
386
445
|
const code = nodeText(children).replace(/\n$/, "");
|
|
387
446
|
const language = fenceLanguage(children);
|
|
@@ -462,7 +521,14 @@ function MarkdownTable({ children, className, ...props }: ComponentPropsWithoutR
|
|
|
462
521
|
);
|
|
463
522
|
}
|
|
464
523
|
|
|
465
|
-
function MarkdownImpl({
|
|
524
|
+
function MarkdownImpl({
|
|
525
|
+
children,
|
|
526
|
+
className,
|
|
527
|
+
streaming = false,
|
|
528
|
+
onSandboxFile,
|
|
529
|
+
renderInteractiveBlock,
|
|
530
|
+
renderImage,
|
|
531
|
+
}: MarkdownProps) {
|
|
466
532
|
// Tip-ink engine for THIS body: created on the first streaming render, kept
|
|
467
533
|
// through a short linger after the stream ends (so the last age window can
|
|
468
534
|
// finish), then dropped so settled bodies pay zero cost. Observing during
|
|
@@ -553,28 +619,39 @@ function MarkdownImpl({ children, className, streaming = false, onSandboxFile }:
|
|
|
553
619
|
const parseText = streaming || revealActive ? softenStreamingMarkdown(children) : children;
|
|
554
620
|
const components = useMemo(() => markdownComponents(onSandboxFile), [onSandboxFile]);
|
|
555
621
|
|
|
622
|
+
const interactiveContext = useMemo(
|
|
623
|
+
() => ({
|
|
624
|
+
source: children,
|
|
625
|
+
...(renderInteractiveBlock ? { render: renderInteractiveBlock } : {}),
|
|
626
|
+
...(renderImage ? { renderImage } : {}),
|
|
627
|
+
}),
|
|
628
|
+
[children, renderInteractiveBlock, renderImage],
|
|
629
|
+
);
|
|
630
|
+
|
|
556
631
|
// `min-w-0` lets the prose shrink inside flex parents (message bubbles) so
|
|
557
632
|
// long links and code blocks wrap/scroll instead of forcing overflow.
|
|
558
633
|
return (
|
|
559
|
-
<
|
|
560
|
-
<
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
<ReactMarkdown
|
|
569
|
-
remarkPlugins={[remarkGfm]}
|
|
570
|
-
rehypePlugins={rehypePlugins}
|
|
571
|
-
components={components}
|
|
572
|
-
urlTransform={markdownUrlTransform}
|
|
634
|
+
<InteractiveContext.Provider value={interactiveContext}>
|
|
635
|
+
<TooltipProvider delayDuration={400}>
|
|
636
|
+
<div
|
|
637
|
+
ref={bodyRef}
|
|
638
|
+
className={cn(
|
|
639
|
+
"og-markdown-body min-w-0 break-words",
|
|
640
|
+
settling && "og-markdown-settle",
|
|
641
|
+
className,
|
|
642
|
+
)}
|
|
573
643
|
>
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
644
|
+
<ReactMarkdown
|
|
645
|
+
remarkPlugins={[remarkGfm]}
|
|
646
|
+
rehypePlugins={rehypePlugins}
|
|
647
|
+
components={components}
|
|
648
|
+
urlTransform={markdownUrlTransform}
|
|
649
|
+
>
|
|
650
|
+
{parseText}
|
|
651
|
+
</ReactMarkdown>
|
|
652
|
+
</div>
|
|
653
|
+
</TooltipProvider>
|
|
654
|
+
</InteractiveContext.Provider>
|
|
578
655
|
);
|
|
579
656
|
}
|
|
580
657
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RollingActivity } from "../timeline/rolling-activity";
|
|
1
2
|
import { GenieLoadingOptionsContext, type GenieLoadingOptions } from "../timeline/genie-loading";
|
|
2
3
|
import { ChildSessionLink } from "./child-session-link";
|
|
3
4
|
import { useStartupDetails } from "../timeline/startup-preference";
|
|
@@ -2526,6 +2527,12 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2526
2527
|
const startupDetails = useStartupDetails();
|
|
2527
2528
|
const enter = useEntranceAnimation();
|
|
2528
2529
|
const settleChrome = useTurnSettleOpen();
|
|
2530
|
+
const previousSingleActivity = useRef<ActivityItem | undefined>(undefined);
|
|
2531
|
+
useLayoutEffect(() => {
|
|
2532
|
+
const work =
|
|
2533
|
+
group.kind === "activity" ? group.items.filter((item) => item.kind !== "startup-phase") : [];
|
|
2534
|
+
previousSingleActivity.current = work.length === 1 ? work[0] : undefined;
|
|
2535
|
+
}, [group]);
|
|
2529
2536
|
const foldMemory = useFoldMemory();
|
|
2530
2537
|
// Settled (or live-fold) activity clusters get a chip. Inside an expanded
|
|
2531
2538
|
// turn that is the second layer — quiet nested chips under the outer turn
|
|
@@ -2552,7 +2559,8 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2552
2559
|
);
|
|
2553
2560
|
}
|
|
2554
2561
|
const settleFold =
|
|
2555
|
-
|
|
2562
|
+
!turnSummary?.rolling &&
|
|
2563
|
+
(group.kind === "turn" ? !!(enter && !insideTurn && !turnDefaultOpen) : liveActivitySettle);
|
|
2556
2564
|
switch (group.kind) {
|
|
2557
2565
|
case "activity":
|
|
2558
2566
|
// Preparation is one quiet surface, not a fold with eight technical steps.
|
|
@@ -2578,6 +2586,24 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2578
2586
|
/>
|
|
2579
2587
|
);
|
|
2580
2588
|
}
|
|
2589
|
+
if (
|
|
2590
|
+
turnSummary?.rolling &&
|
|
2591
|
+
!startupDetails &&
|
|
2592
|
+
group.items.filter((item) => item.kind !== "startup-phase").length === 1
|
|
2593
|
+
) {
|
|
2594
|
+
return (
|
|
2595
|
+
<ActivityRail
|
|
2596
|
+
items={group.items}
|
|
2597
|
+
startupActive={false}
|
|
2598
|
+
bare
|
|
2599
|
+
toolRegistry={toolRegistry}
|
|
2600
|
+
onOpenSession={onOpenSession}
|
|
2601
|
+
onMemoryClick={onMemoryClick}
|
|
2602
|
+
loadRetainedScreenshot={loadRetainedScreenshot}
|
|
2603
|
+
loadRetainedArtifact={loadRetainedArtifact}
|
|
2604
|
+
/>
|
|
2605
|
+
);
|
|
2606
|
+
}
|
|
2581
2607
|
if (insideTurn) {
|
|
2582
2608
|
// Nested chips whenever the parent has ≥2 clusters. During outer settle
|
|
2583
2609
|
// chrome they stay force-open so structure is visible and height stays
|
|
@@ -2589,7 +2615,13 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2589
2615
|
// settled closed pre-wrap was showing as a CHIP, so mounting it closed
|
|
2590
2616
|
// is both the stable height and the honest state — force-opening it
|
|
2591
2617
|
// was the "already-collapsed cluster auto-expands at the end" reopen.
|
|
2592
|
-
const
|
|
2618
|
+
const visibleActivity = group.items.filter((item) => item.kind !== "startup-phase");
|
|
2619
|
+
const singleThought =
|
|
2620
|
+
!startupDetails &&
|
|
2621
|
+
visibleActivity.length === 1 &&
|
|
2622
|
+
visibleActivity[0]?.kind === "reasoning";
|
|
2623
|
+
const useNestedChip =
|
|
2624
|
+
nestClusterChips && activityShouldFold && !containsGeneratedImage && !singleThought;
|
|
2593
2625
|
if (!useNestedChip) {
|
|
2594
2626
|
return (
|
|
2595
2627
|
<ActivityRail
|
|
@@ -2634,7 +2666,20 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2634
2666
|
items={group.items}
|
|
2635
2667
|
outcome={group.outcome}
|
|
2636
2668
|
failureText={group.failureText}
|
|
2637
|
-
defaultOpen={
|
|
2669
|
+
defaultOpen={
|
|
2670
|
+
group.outcome === "failed" || (!turnSummary?.rolling && !activityShouldFold)
|
|
2671
|
+
? true
|
|
2672
|
+
: undefined
|
|
2673
|
+
}
|
|
2674
|
+
liveHeader={
|
|
2675
|
+
turnSummary?.rolling && !group.outcome && !foldLiveCluster ? (
|
|
2676
|
+
<RollingActivity
|
|
2677
|
+
items={group.items}
|
|
2678
|
+
toolRegistry={toolRegistry}
|
|
2679
|
+
previousItem={previousSingleActivity.current}
|
|
2680
|
+
/>
|
|
2681
|
+
) : undefined
|
|
2682
|
+
}
|
|
2638
2683
|
foldKey={group.id}
|
|
2639
2684
|
facets={turnSummary?.facets}
|
|
2640
2685
|
settleFold={settleFold}
|
|
@@ -100,6 +100,11 @@ export type SessionChromeProps = {
|
|
|
100
100
|
queue: UseTurnQueueResult;
|
|
101
101
|
/** Needed for queue edit → composer checkout. Omit with `readOnly`. */
|
|
102
102
|
composer?: ComposerState | undefined;
|
|
103
|
+
/** Focus the composer after a successful queue checkout has applied its draft.
|
|
104
|
+
* Connect to the composer controller's `focusInput`, or a custom input ref.
|
|
105
|
+
* Never called for failed checkout or while replacement awaits confirmation.
|
|
106
|
+
*/
|
|
107
|
+
onComposerFocus?: (() => void) | undefined;
|
|
103
108
|
goal?: UseGoalResult | null | undefined;
|
|
104
109
|
/** Expanded agents body (host supplies tree / list). */
|
|
105
110
|
agentsPanel?: ReactNode;
|
|
@@ -431,6 +436,7 @@ export function SessionChrome({
|
|
|
431
436
|
sessionStatus,
|
|
432
437
|
queue,
|
|
433
438
|
composer,
|
|
439
|
+
onComposerFocus,
|
|
434
440
|
goal,
|
|
435
441
|
agentsPanel,
|
|
436
442
|
commandsPanel,
|
|
@@ -802,7 +808,10 @@ export function SessionChrome({
|
|
|
802
808
|
expectedDraftRevision: composer.draftRevision,
|
|
803
809
|
replaceDraft: true,
|
|
804
810
|
});
|
|
805
|
-
if (checkedOut)
|
|
811
|
+
if (checkedOut) {
|
|
812
|
+
composer.applyDraft(checkedOut);
|
|
813
|
+
onComposerFocus?.();
|
|
814
|
+
}
|
|
806
815
|
})();
|
|
807
816
|
}
|
|
808
817
|
: undefined
|
|
@@ -819,7 +828,10 @@ export function SessionChrome({
|
|
|
819
828
|
expectedDraftRevision: composer.draftRevision,
|
|
820
829
|
replaceDraft: false,
|
|
821
830
|
});
|
|
822
|
-
if (checkedOut)
|
|
831
|
+
if (checkedOut) {
|
|
832
|
+
composer.applyDraft(checkedOut);
|
|
833
|
+
onComposerFocus?.();
|
|
834
|
+
}
|
|
823
835
|
})();
|
|
824
836
|
},
|
|
825
837
|
);
|
|
@@ -159,9 +159,19 @@ export function useComputerFrameStream(
|
|
|
159
159
|
pendingFrame = null;
|
|
160
160
|
};
|
|
161
161
|
|
|
162
|
+
const clearRecoveryTimers = () => {
|
|
163
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
164
|
+
if (expiryTimer) clearTimeout(expiryTimer);
|
|
165
|
+
reconnectTimer = null;
|
|
166
|
+
expiryTimer = null;
|
|
167
|
+
};
|
|
168
|
+
|
|
162
169
|
const scheduleReconnect = () => {
|
|
163
170
|
if (disposed || reconnectTimer) return;
|
|
164
|
-
if (failures >= MAX_AUTOMATIC_RECONNECTS_WITHOUT_A_FRAME)
|
|
171
|
+
if (failures >= MAX_AUTOMATIC_RECONNECTS_WITHOUT_A_FRAME) {
|
|
172
|
+
clearRecoveryTimers();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
165
175
|
const delay = Math.min(5_000, 250 * 2 ** Math.min(failures, 5));
|
|
166
176
|
failures += 1;
|
|
167
177
|
setResult((current) => ({ ...current, state: "reconnecting" }));
|
|
@@ -177,6 +187,7 @@ export function useComputerFrameStream(
|
|
|
177
187
|
setResult((current) => ({ ...current, state: "error", error }));
|
|
178
188
|
clearSocket();
|
|
179
189
|
if (reconnectAutomatically) scheduleReconnect();
|
|
190
|
+
else clearRecoveryTimers();
|
|
180
191
|
};
|
|
181
192
|
|
|
182
193
|
const onOpen = (source: ComputerFrameWebSocket) => {
|
|
@@ -295,8 +306,7 @@ export function useComputerFrameStream(
|
|
|
295
306
|
|
|
296
307
|
const onClose = (source: ComputerFrameWebSocket) => {
|
|
297
308
|
if (disposed || source !== socket) return;
|
|
298
|
-
|
|
299
|
-
scheduleReconnect();
|
|
309
|
+
fail(new Error("Desktop view lost connection."));
|
|
300
310
|
};
|
|
301
311
|
|
|
302
312
|
const connectSocket = () => {
|
|
@@ -366,6 +376,9 @@ export function useComputerFrameStream(
|
|
|
366
376
|
throw new Error("desktop attachment does not match the requested resource");
|
|
367
377
|
}
|
|
368
378
|
controllerGeneration = attachment.controllerGeneration;
|
|
379
|
+
// A fresh attachment owns a new producer whose sequence can restart.
|
|
380
|
+
// Transport reconnects reuse the attachment and retain their sequence fence.
|
|
381
|
+
latestRef.current = { key: "", sequence: -1 };
|
|
369
382
|
activeAttachment = attachment;
|
|
370
383
|
activeStream = attachment.stream;
|
|
371
384
|
attachmentExpiresAt = Date.parse(attachment.expiresAt);
|
|
@@ -406,8 +419,7 @@ export function useComputerFrameStream(
|
|
|
406
419
|
disposed = true;
|
|
407
420
|
pendingFrame = null;
|
|
408
421
|
attachmentAbort.abort();
|
|
409
|
-
|
|
410
|
-
if (expiryTimer) clearTimeout(expiryTimer);
|
|
422
|
+
clearRecoveryTimers();
|
|
411
423
|
clearSocket(true);
|
|
412
424
|
};
|
|
413
425
|
}, [client, enabled, nonce, options.computerSessionId, options.targetId, workspaceId]);
|
|
@@ -117,6 +117,33 @@ export function useComputerSession(options: UseComputerSessionOptions): UseCompu
|
|
|
117
117
|
if (!mountedRef.current || requestRef.current.id !== id) return;
|
|
118
118
|
const targets = sortComputerTargets(targetResponse.targets);
|
|
119
119
|
const selected = chooseTarget(targets, selectedTargetIdRef.current, session.platform);
|
|
120
|
+
// Discovery is useful independently of semantic observation. Publish it
|
|
121
|
+
// now so a slow/unresponsive application cannot block the frame stream
|
|
122
|
+
// or prevent the person from choosing a different window or screen.
|
|
123
|
+
const previous = observationRef.current.observation;
|
|
124
|
+
const retainedObservation =
|
|
125
|
+
selected &&
|
|
126
|
+
previous?.target.id === selected.id &&
|
|
127
|
+
previous.target.controllerGeneration === selected.controllerGeneration &&
|
|
128
|
+
previous.target.targetGeneration === selected.targetGeneration
|
|
129
|
+
? previous
|
|
130
|
+
: null;
|
|
131
|
+
selectedTargetIdRef.current = selected?.id ?? null;
|
|
132
|
+
targetsRef.current = { computerSessionId, targets };
|
|
133
|
+
observationRef.current = { computerSessionId, observation: retainedObservation };
|
|
134
|
+
setState((current) =>
|
|
135
|
+
current.computerSessionId === computerSessionId
|
|
136
|
+
? {
|
|
137
|
+
...current,
|
|
138
|
+
session,
|
|
139
|
+
targets,
|
|
140
|
+
selectedTargetId: selected?.id ?? null,
|
|
141
|
+
observation: retainedObservation,
|
|
142
|
+
loading: false,
|
|
143
|
+
error: null,
|
|
144
|
+
}
|
|
145
|
+
: current,
|
|
146
|
+
);
|
|
120
147
|
const observation = selected
|
|
121
148
|
? await client.observeComputerTarget(workspaceId, computerSessionId, selected.id, {
|
|
122
149
|
signal: controller.signal,
|
|
@@ -303,7 +330,15 @@ export function useComputerSession(options: UseComputerSessionOptions): UseCompu
|
|
|
303
330
|
? (targetsRef.current.targets.find((candidate) => candidate.id === frame.targetId) ??
|
|
304
331
|
(currentObservation?.target.id === frame.targetId ? currentObservation.target : null))
|
|
305
332
|
: null;
|
|
306
|
-
const
|
|
333
|
+
const inputTarget =
|
|
334
|
+
(action.type === "keyboard" || action.type === "clipboard") &&
|
|
335
|
+
targetsRef.current.computerSessionId === computerSessionId
|
|
336
|
+
? targetsRef.current.targets.find(
|
|
337
|
+
(candidate) => candidate.id === selectedTargetIdRef.current,
|
|
338
|
+
)
|
|
339
|
+
: null;
|
|
340
|
+
const target =
|
|
341
|
+
frameTarget ?? focusTarget ?? currentObservation?.target ?? inputTarget ?? null;
|
|
307
342
|
if (!target) throw new Error("The desktop target is not ready for input.");
|
|
308
343
|
if (frame && frame.targetId !== selectedTargetIdRef.current) {
|
|
309
344
|
throw new Error("The displayed desktop frame is no longer selected.");
|
|
@@ -13,6 +13,8 @@ export type UseFileAttachmentsOptions = EmbeddedFileAttachmentClientOverride & {
|
|
|
13
13
|
* UseFileAttachmentsResult.addFiles} (the explicit picker / drop path)
|
|
14
14
|
* bypasses it.
|
|
15
15
|
*/
|
|
16
|
+
/** Retain original uploads in the chat owner’s personal scope when private. */
|
|
17
|
+
scope?: "workspace" | "personal";
|
|
16
18
|
pasteFilter?: ((file: File) => boolean) | undefined;
|
|
17
19
|
};
|
|
18
20
|
|
|
@@ -212,6 +214,7 @@ export function useFileAttachments(
|
|
|
212
214
|
filename: file.name || "file",
|
|
213
215
|
contentType: file.type || "application/octet-stream",
|
|
214
216
|
data: file,
|
|
217
|
+
...(options.scope ? { scope: options.scope } : {}),
|
|
215
218
|
})
|
|
216
219
|
.then((asset) => {
|
|
217
220
|
if (scopeGeneration.current !== generation) return;
|
|
@@ -252,7 +255,7 @@ export function useFileAttachments(
|
|
|
252
255
|
);
|
|
253
256
|
});
|
|
254
257
|
},
|
|
255
|
-
[client, workspaceId],
|
|
258
|
+
[client, workspaceId, options.scope],
|
|
256
259
|
);
|
|
257
260
|
|
|
258
261
|
const addFiles = useCallback(
|
package/src/hooks/use-goal.ts
CHANGED
|
@@ -138,7 +138,9 @@ export function useGoal(
|
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
if (sharedFeed) {
|
|
141
|
-
|
|
141
|
+
// A shared log supplies invalidations, not the authoritative goal snapshot.
|
|
142
|
+
setLoading(true);
|
|
143
|
+
void load();
|
|
142
144
|
return () => {
|
|
143
145
|
retireLoads();
|
|
144
146
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { retainedImageId } from "./components/markdown";
|
|
1
2
|
// oxlint-disable-next-line typescript/triple-slash-reference -- package consumers must load the ambient type for this optional untyped peer without emitting a runtime import.
|
|
2
3
|
/// <reference path="./types/external.d.ts" />
|
|
3
4
|
|
|
@@ -520,7 +521,11 @@ export {
|
|
|
520
521
|
sandboxFileLocationFromHref,
|
|
521
522
|
sandboxFilePathFromHref,
|
|
522
523
|
} from "./components/markdown";
|
|
523
|
-
export type {
|
|
524
|
+
export type {
|
|
525
|
+
MarkdownProps,
|
|
526
|
+
MarkdownInteractiveBlock,
|
|
527
|
+
SandboxFileLocation,
|
|
528
|
+
} from "./components/markdown";
|
|
524
529
|
export { CopyButton, CopyHoverFrame } from "./components/copy-button";
|
|
525
530
|
export { copyTextToClipboard, tableElementToTsv } from "./lib/clipboard";
|
|
526
531
|
export { SessionStatus, StatusDot, SESSION_STATUS_META } from "./components/session-status";
|
|
@@ -612,3 +617,9 @@ export {
|
|
|
612
617
|
tryParseJson,
|
|
613
618
|
} from "./lib/format";
|
|
614
619
|
export { SessionCommandsPanel } from "./components/session-commands-panel";
|
|
620
|
+
|
|
621
|
+
export { KnowledgeActivityProvider } from "./timeline/knowledge-receipt";
|
|
622
|
+
export type { KnowledgeActivityActions } from "./timeline/knowledge-receipt";
|
|
623
|
+
|
|
624
|
+
export { StartupTimings } from "./timeline/startup-timings";
|
|
625
|
+
export { setStartupDetails, useStartupDetails } from "./timeline/startup-preference";
|
package/src/session-ui.ts
CHANGED
|
@@ -46,6 +46,9 @@ export type {
|
|
|
46
46
|
SessionChromeSignalTone,
|
|
47
47
|
} from "./components/session-chrome";
|
|
48
48
|
export { SessionCommandsPanel } from "./components/session-commands-panel";
|
|
49
|
+
|
|
50
|
+
export { KnowledgeActivityProvider } from "./timeline/knowledge-receipt";
|
|
51
|
+
export type { KnowledgeActivityActions } from "./timeline/knowledge-receipt";
|
|
49
52
|
export { StartupTimings } from "./timeline/startup-timings";
|
|
50
53
|
export { useStartupDetails, setStartupDetails } from "./timeline/startup-preference";
|
|
51
54
|
export type { GenieLoadingOptions } from "./timeline/genie-loading";
|
package/src/sites-ui.tsx
CHANGED
|
@@ -29,10 +29,11 @@ type SiteScope = { client: SiteClient; workspaceId: string; className?: string }
|
|
|
29
29
|
/** Shared native/embedded read boundary: pin content to the observed version,
|
|
30
30
|
* and never accept a response belonging to another workspace or Site. */
|
|
31
31
|
export async function loadSiteSnapshot(
|
|
32
|
-
client: Pick<SiteClient, "getWorkspaceArtifact" | "getWorkspaceArtifactHtml"
|
|
32
|
+
client: Pick<SiteClient, "getWorkspaceArtifact" | "getWorkspaceArtifactHtml"> &
|
|
33
|
+
Partial<Pick<OpenGeniClient, "getWorkspaceArtifactContent">>,
|
|
33
34
|
workspaceId: string,
|
|
34
35
|
siteId: string,
|
|
35
|
-
options: { signal?: AbortSignal; includeArchivedContent?: boolean } = {},
|
|
36
|
+
options: { signal?: AbortSignal; includeArchivedContent?: boolean; versionId?: string } = {},
|
|
36
37
|
): Promise<{
|
|
37
38
|
detail: WorkspaceArtifactDetailResponse;
|
|
38
39
|
content: SiteDisplayContent | null;
|
|
@@ -42,19 +43,35 @@ export async function loadSiteSnapshot(
|
|
|
42
43
|
options.signal?.throwIfAborted();
|
|
43
44
|
if (detail.artifact.id !== siteId || detail.artifact.workspaceId !== workspaceId)
|
|
44
45
|
throw new Error("Site scope mismatch");
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const selectedVersion = options.versionId
|
|
47
|
+
? detail.versions.find((version) => version.id === options.versionId)
|
|
48
|
+
: detail.artifact.currentVersion;
|
|
49
|
+
const versionId = options.versionId ?? selectedVersion?.id;
|
|
50
|
+
let content: SiteDisplayContent | null =
|
|
51
|
+
selectedVersion &&
|
|
52
|
+
versionId &&
|
|
53
|
+
(detail.artifact.status === "active" || options.includeArchivedContent)
|
|
48
54
|
? {
|
|
49
55
|
artifactId: siteId,
|
|
50
56
|
versionId,
|
|
51
|
-
requestedTools:
|
|
57
|
+
requestedTools: selectedVersion!.requestedTools,
|
|
52
58
|
html: await client.getWorkspaceArtifactHtml(workspaceId, siteId, {
|
|
53
59
|
...requestOptions,
|
|
54
60
|
versionId,
|
|
55
61
|
}),
|
|
56
62
|
}
|
|
57
63
|
: null;
|
|
64
|
+
if (
|
|
65
|
+
versionId &&
|
|
66
|
+
!selectedVersion &&
|
|
67
|
+
(detail.artifact.status === "active" || options.includeArchivedContent)
|
|
68
|
+
) {
|
|
69
|
+
if (!client.getWorkspaceArtifactContent) throw new Error("Site version unavailable");
|
|
70
|
+
content = await client.getWorkspaceArtifactContent(workspaceId, siteId, {
|
|
71
|
+
...requestOptions,
|
|
72
|
+
versionId,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
58
75
|
options.signal?.throwIfAborted();
|
|
59
76
|
if (content && (content.artifactId !== siteId || content.versionId !== versionId))
|
|
60
77
|
throw new Error("Site content mismatch");
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { KnowledgeReceiptRow } from "./knowledge-receipt";
|
|
1
2
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
2
3
|
import { GenieLoading } from "./genie-loading";
|
|
3
4
|
import { useStartupDetails } from "./startup-preference";
|
|
4
5
|
import { ArrowRightIcon, BotIcon, BrainCircuitIcon } from "lucide-react";
|
|
5
|
-
import { lazy, Suspense, useLayoutEffect, useRef, useState } from "react";
|
|
6
|
+
import { lazy, Suspense, useContext, useLayoutEffect, useRef, useState } from "react";
|
|
6
7
|
import { jsx as rowJsx, jsxs as rowJsxs } from "react/jsx-runtime";
|
|
7
8
|
import { Markdown } from "../components/markdown";
|
|
8
9
|
import { cn } from "../lib/cn";
|
|
@@ -11,7 +12,13 @@ import { defaultToolRegistry } from "./tool-renderers";
|
|
|
11
12
|
import { useEntranceAnimation, useEntranceAnimationLive } from "./entrance";
|
|
12
13
|
import type { RetainedArtifactLoader, RetainedScreenshotLoader, ToolRegistry } from "./registry";
|
|
13
14
|
import { useSeenActivityIds } from "./seen-activity-ids";
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
BodyNote,
|
|
17
|
+
PayloadBlock,
|
|
18
|
+
ActivityDisclosure,
|
|
19
|
+
CompactActivityContext,
|
|
20
|
+
ToolCallTruncationProvider,
|
|
21
|
+
} from "./shared";
|
|
15
22
|
import { toolDisplayName } from "./tool-display-name";
|
|
16
23
|
import type { ActivityItem, MemoryItem, WorkerItem } from "./types";
|
|
17
24
|
|
|
@@ -214,7 +221,7 @@ function assertNever(item: never): never {
|
|
|
214
221
|
throw new Error(`ActivityRail: unhandled activity item ${JSON.stringify(item)}`);
|
|
215
222
|
}
|
|
216
223
|
|
|
217
|
-
function renderActivity(
|
|
224
|
+
export function renderActivity(
|
|
218
225
|
item: ActivityItem,
|
|
219
226
|
toolRegistry: ToolRegistry,
|
|
220
227
|
onOpenSession: ((sessionId: string) => void) | undefined,
|
|
@@ -235,7 +242,6 @@ function renderActivity(
|
|
|
235
242
|
t={toolDisplayName}
|
|
236
243
|
b={BotIcon}
|
|
237
244
|
m={Markdown}
|
|
238
|
-
r={truncate}
|
|
239
245
|
j={rowJsx}
|
|
240
246
|
s={rowJsxs}
|
|
241
247
|
/>
|
|
@@ -255,6 +261,16 @@ function renderActivity(
|
|
|
255
261
|
}
|
|
256
262
|
case "worker":
|
|
257
263
|
return <WorkerRow item={item} onOpenSession={onOpenSession} />;
|
|
264
|
+
case "knowledge":
|
|
265
|
+
return (
|
|
266
|
+
<KnowledgeReceiptRow
|
|
267
|
+
outcome={item.outcome}
|
|
268
|
+
entryId={item.entryId}
|
|
269
|
+
fileId={item.fileId}
|
|
270
|
+
title={item.filename}
|
|
271
|
+
source={Boolean(item.fileId)}
|
|
272
|
+
/>
|
|
273
|
+
);
|
|
258
274
|
case "memory":
|
|
259
275
|
return <MemoryRow item={item} onMemoryClick={onMemoryClick} />;
|
|
260
276
|
case "fleet-decision":
|
|
@@ -380,6 +396,7 @@ function WorkerRow({
|
|
|
380
396
|
item: WorkerItem;
|
|
381
397
|
onOpenSession?: ((sessionId: string) => void) | undefined;
|
|
382
398
|
}) {
|
|
399
|
+
const compact = useContext(CompactActivityContext);
|
|
383
400
|
const running = item.status === "running";
|
|
384
401
|
const failed = item.status === "failed";
|
|
385
402
|
const cancelled = item.status === "cancelled";
|
|
@@ -399,6 +416,16 @@ function WorkerRow({
|
|
|
399
416
|
: cancelled
|
|
400
417
|
? "Worker interrupted"
|
|
401
418
|
: "Worker messaged";
|
|
419
|
+
if (compact) {
|
|
420
|
+
return (
|
|
421
|
+
<ActivityDisclosure
|
|
422
|
+
icon={<BotIcon className="size-3.5" />}
|
|
423
|
+
title={title}
|
|
424
|
+
preview={item.prompt}
|
|
425
|
+
running={running}
|
|
426
|
+
/>
|
|
427
|
+
);
|
|
428
|
+
}
|
|
402
429
|
// A worker is a first-class actor but still a STEP on the rail — a borderless
|
|
403
430
|
// row (no card), aligned to its sibling tool rows: the chevron column is an
|
|
404
431
|
// empty spacer (a worker doesn't expand), then the bot glyph, then the title
|