@opengeni/react 0.45.0 → 0.48.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 +68 -12
- package/dist/{chunk-U3CSCMWA.js → chunk-FK6VG4LL.js} +9 -13
- package/dist/chunk-FK6VG4LL.js.map +1 -0
- package/dist/{chunk-HFO4ERGQ.js → chunk-FWXGSNXT.js} +2 -2
- package/dist/{chunk-SRFUT2ZU.js → chunk-I4MPFDRP.js} +119 -63
- package/dist/chunk-I4MPFDRP.js.map +1 -0
- package/dist/{chunk-EB67J347.js → chunk-KZ73RL76.js} +111 -31
- package/dist/chunk-KZ73RL76.js.map +1 -0
- package/dist/{chunk-LWR4MXSS.js → chunk-LHLYWG5R.js} +10 -1
- package/dist/chunk-LHLYWG5R.js.map +1 -0
- package/dist/{chunk-L3EWO3UK.js → chunk-NJ7M2B6Z.js} +445 -84
- package/dist/chunk-NJ7M2B6Z.js.map +1 -0
- package/dist/{chunk-VCBFUSUZ.js → chunk-O4VXOBGF.js} +3 -3
- package/dist/{chunk-VCBFUSUZ.js.map → chunk-O4VXOBGF.js.map} +1 -1
- package/dist/{chunk-6EDV6YFE.js → chunk-OL6QVXGF.js} +121 -29
- package/dist/chunk-OL6QVXGF.js.map +1 -0
- package/dist/chunk-VZTQ73XT.js +86 -0
- package/dist/chunk-VZTQ73XT.js.map +1 -0
- package/dist/{chunk-EHSYZ4KP.js → chunk-WYA65Y3F.js} +16 -16
- package/dist/chunk-WYA65Y3F.js.map +1 -0
- package/dist/{chunk-FD72GJMK.js → chunk-XCNTXWBB.js} +274 -125
- package/dist/chunk-XCNTXWBB.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/components/chat-composer.d.ts +7 -2
- package/dist/components/composer-transcription-control.d.ts +2 -0
- package/dist/components/composer.d.ts +14 -0
- package/dist/components/machines/machine-detail.d.ts +3 -1
- package/dist/components/machines-dashboard.d.ts +3 -3
- package/dist/components/message-timeline.d.ts +12 -2
- package/dist/components/sandbox-files.d.ts +2 -2
- package/dist/components/sandbox-terminal.d.ts +29 -0
- package/dist/components/sandbox-workspace.d.ts +5 -1
- package/dist/components/workspace-dock.d.ts +3 -1
- package/dist/composer.d.ts +2 -2
- package/dist/composer.js +6 -6
- package/dist/hooks/internal.d.ts +6 -0
- package/dist/hooks/use-codex-accounts.d.ts +1 -1
- package/dist/hooks/use-environments.d.ts +10 -4
- package/dist/hooks/use-machines.d.ts +7 -0
- package/dist/hooks/use-sandbox-files.d.ts +19 -0
- package/dist/hooks/use-sandbox-git.d.ts +7 -0
- package/dist/hooks/use-transcription.d.ts +2 -2
- package/dist/hooks/use-voice-input.d.ts +13 -4
- package/dist/index.d.ts +6 -5
- package/dist/index.js +904 -538
- package/dist/index.js.map +1 -1
- package/dist/lib/composer-responsive-context.d.ts +9 -0
- package/dist/lib/use-portal-token-style.d.ts +14 -3
- package/dist/machines.js +2 -2
- package/dist/{queue-surface-implementation-J4XJFKRW.js → queue-surface-implementation-U5JFG7HT.js} +19 -18
- package/dist/queue-surface-implementation-U5JFG7HT.js.map +1 -0
- package/dist/realtime/realtime-control.d.ts +9 -0
- package/dist/realtime.js +29 -32
- package/dist/realtime.js.map +1 -1
- package/dist/session-ui.js +5 -5
- package/dist/session.js +4 -4
- package/dist/timeline/activity-rail.d.ts +3 -2
- package/dist/timeline/index.d.ts +2 -2
- package/dist/timeline/parsers.d.ts +3 -3
- package/dist/timeline/registry.d.ts +3 -0
- package/dist/timeline/screenshot-lightbox.d.ts +1 -1
- package/dist/timeline/types.d.ts +4 -0
- package/dist/voice-recording-store.d.ts +8 -2
- package/package.json +20 -4
- package/src/client.ts +3 -0
- package/src/components/chat-composer.tsx +12 -2
- package/src/components/code-editor.tsx +1 -1
- package/src/components/command-palette.tsx +1 -1
- package/src/components/composer-transcription-control.tsx +41 -16
- package/src/components/composer.tsx +52 -26
- package/src/components/copy-button.tsx +2 -2
- package/src/components/desktop-viewer.tsx +2 -2
- package/src/components/file-browser.tsx +39 -17
- package/src/components/human-input-form.tsx +2 -2
- package/src/components/machine-card.tsx +1 -1
- package/src/components/machines/machine-detail.tsx +39 -15
- package/src/components/machines-dashboard.tsx +7 -6
- package/src/components/message-timeline.tsx +44 -11
- package/src/components/model-picker.tsx +2 -2
- package/src/components/model-policy-picker.tsx +16 -15
- package/src/components/queue-surface-implementation.tsx +20 -16
- package/src/components/queue-surface-state.tsx +2 -2
- package/src/components/sandbox-files.tsx +140 -60
- package/src/components/sandbox-terminal.tsx +24 -5
- package/src/components/sandbox-workspace.tsx +180 -142
- package/src/components/session-chrome.tsx +14 -14
- package/src/components/tooltip.tsx +13 -16
- package/src/components/user-message-body.tsx +1 -1
- package/src/components/workbench-changes.tsx +4 -4
- package/src/components/workspace-dock.tsx +13 -5
- package/src/composer.ts +3 -0
- package/src/hooks/internal.ts +132 -38
- package/src/hooks/use-codex-accounts.ts +4 -4
- package/src/hooks/use-environments.ts +24 -5
- package/src/hooks/use-file-attachments.ts +70 -32
- package/src/hooks/use-goal.ts +17 -5
- package/src/hooks/use-machines.ts +47 -1
- package/src/hooks/use-sandbox-files.ts +310 -59
- package/src/hooks/use-sandbox-git.ts +148 -41
- package/src/hooks/use-session-capabilities.ts +35 -24
- package/src/hooks/use-session-events.ts +18 -4
- package/src/hooks/use-transcription.ts +4 -14
- package/src/hooks/use-turn-queue.ts +17 -5
- package/src/hooks/use-voice-input.ts +481 -36
- package/src/hooks/use-windowed-sections.ts +9 -7
- package/src/index.ts +11 -6
- package/src/lib/composer-responsive-context.ts +15 -0
- package/src/lib/use-portal-token-style.ts +60 -15
- package/src/provider.tsx +52 -7
- package/src/realtime/dropdown-menu.tsx +15 -19
- package/src/realtime/realtime-control.tsx +18 -7
- package/src/timeline/activity-rail.tsx +15 -6
- package/src/timeline/index.ts +1 -1
- package/src/timeline/parsers.ts +33 -20
- package/src/timeline/projection.ts +23 -0
- package/src/timeline/registry.ts +7 -0
- package/src/timeline/screenshot-lightbox.tsx +15 -4
- package/src/timeline/shared.tsx +8 -8
- package/src/timeline/tool-renderers.tsx +166 -22
- package/src/timeline/turn-summary.tsx +6 -6
- package/src/timeline/types.ts +4 -0
- package/src/voice-recording-store.ts +15 -0
- package/styles/compiled.css +5065 -0
- package/styles/compiled.d.ts +2 -0
- package/styles/effective-tokens.css +27 -0
- package/styles/index.css +3 -0
- package/styles/responsive.css +196 -0
- package/styles/responsive.d.ts +2 -0
- package/styles/tokens.css +50 -17
- package/dist/chunk-6EDV6YFE.js.map +0 -1
- package/dist/chunk-EB67J347.js.map +0 -1
- package/dist/chunk-EHSYZ4KP.js.map +0 -1
- package/dist/chunk-FD72GJMK.js.map +0 -1
- package/dist/chunk-JB5725BI.js +0 -48
- package/dist/chunk-JB5725BI.js.map +0 -1
- package/dist/chunk-L3EWO3UK.js.map +0 -1
- package/dist/chunk-LWR4MXSS.js.map +0 -1
- package/dist/chunk-SRFUT2ZU.js.map +0 -1
- package/dist/chunk-U3CSCMWA.js.map +0 -1
- package/dist/queue-surface-implementation-J4XJFKRW.js.map +0 -1
- /package/dist/{chunk-HFO4ERGQ.js.map → chunk-FWXGSNXT.js.map} +0 -0
|
@@ -540,6 +540,18 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
540
540
|
|
|
541
541
|
case "tool.auth_needed":
|
|
542
542
|
case "credential.auth_needed": {
|
|
543
|
+
if (
|
|
544
|
+
event.type === "tool.auth_needed" &&
|
|
545
|
+
!stringValue(payload.toolName) &&
|
|
546
|
+
stringValue(payload.serverId) !== "codex_apps"
|
|
547
|
+
) {
|
|
548
|
+
// Historical optional-MCP initialize/tools-list credential misses are
|
|
549
|
+
// setup availability, not evidence of a concrete conversational tool
|
|
550
|
+
// call. Keep the raw event in Debug/audit but do not manufacture an
|
|
551
|
+
// actionable reconnect card in the transcript. Actual tools/call auth
|
|
552
|
+
// failures carry a concrete toolName and remain visible.
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
543
555
|
// Keep the whole structured payload — the renderer turns it into a clean
|
|
544
556
|
// inline reconnect card, and the app starts the recovery flow off the
|
|
545
557
|
// connectionId/resource. Losing it to a plain-text notice was the ugly
|
|
@@ -549,6 +561,8 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
549
561
|
kind: "auth-needed",
|
|
550
562
|
id: event.id,
|
|
551
563
|
turnId,
|
|
564
|
+
serverId: typeof payload.serverId === "string" ? payload.serverId : null,
|
|
565
|
+
source: event.type === "tool.auth_needed" ? "tool" : "credential",
|
|
552
566
|
providerDomain: stringValue(payload.providerDomain),
|
|
553
567
|
connectionId: typeof payload.connectionId === "string" ? payload.connectionId : null,
|
|
554
568
|
reason: authNeededReason(payload.reason),
|
|
@@ -1609,6 +1623,15 @@ export function extractSessionRef(value: unknown, depth = 0): string | null {
|
|
|
1609
1623
|
return null;
|
|
1610
1624
|
}
|
|
1611
1625
|
const record = value as Record<string, unknown>;
|
|
1626
|
+
const receiptResource = asRecord(record.resource);
|
|
1627
|
+
if (
|
|
1628
|
+
record.receiptVersion === "mcp-mutation-receipt.v1" &&
|
|
1629
|
+
receiptResource.type === "session" &&
|
|
1630
|
+
typeof receiptResource.id === "string" &&
|
|
1631
|
+
looksLikeId(receiptResource.id)
|
|
1632
|
+
) {
|
|
1633
|
+
return receiptResource.id;
|
|
1634
|
+
}
|
|
1612
1635
|
if (typeof record.sessionId === "string" && looksLikeId(record.sessionId)) {
|
|
1613
1636
|
return record.sessionId;
|
|
1614
1637
|
}
|
package/src/timeline/registry.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RetainedArtifactReference } from "@opengeni/sdk";
|
|
1
2
|
import type { ComponentType } from "react";
|
|
2
3
|
import { mcpToolLeaf } from "./tool-display-name";
|
|
3
4
|
import type { ToolCallItem } from "./types";
|
|
@@ -22,8 +23,14 @@ import type { ToolCallItem } from "./types";
|
|
|
22
23
|
|
|
23
24
|
export type ToolRendererProps = {
|
|
24
25
|
item: ToolCallItem;
|
|
26
|
+
loadRetainedScreenshot?: RetainedScreenshotLoader | undefined;
|
|
25
27
|
};
|
|
26
28
|
|
|
29
|
+
export type RetainedScreenshotLoader = (
|
|
30
|
+
artifact: RetainedArtifactReference,
|
|
31
|
+
signal: AbortSignal,
|
|
32
|
+
) => Promise<Uint8Array | null>;
|
|
33
|
+
|
|
27
34
|
export type ToolRenderer = ComponentType<ToolRendererProps>;
|
|
28
35
|
|
|
29
36
|
/** A registry entry: which key it matches and the component that renders it. */
|
|
@@ -3,6 +3,7 @@ import { AnimatePresence, motion } from "motion/react";
|
|
|
3
3
|
import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from "react";
|
|
4
4
|
import { Dialog } from "radix-ui";
|
|
5
5
|
import { cn } from "../lib/cn";
|
|
6
|
+
import { usePortalTokenStyle } from "../lib/use-portal-token-style";
|
|
6
7
|
|
|
7
8
|
/* ----------------------------------------------------------------------------
|
|
8
9
|
Screenshot lightbox
|
|
@@ -18,7 +19,7 @@ import { cn } from "../lib/cn";
|
|
|
18
19
|
-------------------------------------------------------------------------- */
|
|
19
20
|
|
|
20
21
|
type LightboxController = {
|
|
21
|
-
open: (src: string, caption?: string) => void;
|
|
22
|
+
open: (src: string, caption?: string, source?: HTMLElement | null) => void;
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
const LightboxContext = createContext<LightboxController | null>(null);
|
|
@@ -59,17 +60,25 @@ export function LightboxProvider({ children }: { children: ReactNode }) {
|
|
|
59
60
|
|
|
60
61
|
function LightboxRoot({ children }: { children: ReactNode }) {
|
|
61
62
|
const [state, setState] = useState<{ src: string; caption?: string } | null>(null);
|
|
63
|
+
const [tokenSource, setTokenSource] = useState<HTMLElement | null>(null);
|
|
64
|
+
const portalStyle = usePortalTokenStyle(tokenSource);
|
|
62
65
|
|
|
63
|
-
const open = useCallback((src: string, caption?: string) => {
|
|
66
|
+
const open = useCallback((src: string, caption?: string, source?: HTMLElement | null) => {
|
|
67
|
+
setTokenSource(source ?? null);
|
|
64
68
|
setState(caption ? { src, caption } : { src });
|
|
65
69
|
}, []);
|
|
66
70
|
|
|
71
|
+
const close = useCallback(() => {
|
|
72
|
+
setState(null);
|
|
73
|
+
setTokenSource(null);
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
67
76
|
const controller = useMemo<LightboxController>(() => ({ open }), [open]);
|
|
68
77
|
|
|
69
78
|
return (
|
|
70
79
|
<LightboxContext.Provider value={controller}>
|
|
71
80
|
{children}
|
|
72
|
-
<Dialog.Root open={state !== null} onOpenChange={(next) => !next &&
|
|
81
|
+
<Dialog.Root open={state !== null} onOpenChange={(next) => !next && close()}>
|
|
73
82
|
<AnimatePresence>
|
|
74
83
|
{state !== null ? (
|
|
75
84
|
<Dialog.Portal forceMount>
|
|
@@ -80,6 +89,7 @@ function LightboxRoot({ children }: { children: ReactNode }) {
|
|
|
80
89
|
exit={{ opacity: 0 }}
|
|
81
90
|
transition={{ duration: 0.15 }}
|
|
82
91
|
className="og-root fixed inset-0 z-50 bg-black/90 backdrop-blur-md"
|
|
92
|
+
style={portalStyle}
|
|
83
93
|
/>
|
|
84
94
|
</Dialog.Overlay>
|
|
85
95
|
<Dialog.Content
|
|
@@ -101,10 +111,11 @@ function LightboxRoot({ children }: { children: ReactNode }) {
|
|
|
101
111
|
// figcaption's "click outside to close" affordance is real.
|
|
102
112
|
onClick={(event) => {
|
|
103
113
|
if (event.target === event.currentTarget) {
|
|
104
|
-
|
|
114
|
+
close();
|
|
105
115
|
}
|
|
106
116
|
}}
|
|
107
117
|
className="og-root fixed inset-0 z-50 flex items-center justify-center p-6 sm:p-12"
|
|
118
|
+
style={portalStyle}
|
|
108
119
|
>
|
|
109
120
|
<Dialog.Title className="sr-only">Screenshot</Dialog.Title>
|
|
110
121
|
{/* The figure is one self-contained object: image, caption, and
|
package/src/timeline/shared.tsx
CHANGED
|
@@ -154,16 +154,16 @@ export function ActivityDisclosure({
|
|
|
154
154
|
const rowClass = cn(
|
|
155
155
|
"group/disclosure flex w-full min-w-0 items-center gap-2 rounded-og-sm px-1.5 py-1.5 text-left text-og-base",
|
|
156
156
|
"text-og-fg-muted transition-colors duration-150",
|
|
157
|
-
// A tool row is a touch target on coarse pointers: grow its
|
|
158
|
-
//
|
|
159
|
-
"pointer-coarse:py-2.5",
|
|
157
|
+
// A tool row is a touch target on coarse pointers: grow its hit area to the
|
|
158
|
+
// 44px mobile minimum without loosening the dense desktop rail.
|
|
159
|
+
"pointer-coarse:min-h-11 pointer-coarse:py-2.5",
|
|
160
160
|
);
|
|
161
161
|
// The chevron rotates to point down when open; it tracks `data-state` on this
|
|
162
162
|
// same row (the Trigger), so the affordance never freezes.
|
|
163
163
|
const inner = (
|
|
164
164
|
<>
|
|
165
165
|
{hasBody ? (
|
|
166
|
-
<ChevronRightIcon className="size-3.5 shrink-0 text-og-fg-subtle transition-transform duration-[var(--
|
|
166
|
+
<ChevronRightIcon className="size-3.5 shrink-0 text-og-fg-subtle transition-transform duration-[var(--_og-duration-disclose)] ease-og-in-out group-data-[state=open]/disclosure:rotate-90" />
|
|
167
167
|
) : (
|
|
168
168
|
<span className="size-3.5 shrink-0" />
|
|
169
169
|
)}
|
|
@@ -226,7 +226,7 @@ export function ActivityDisclosure({
|
|
|
226
226
|
data-status={dataStatus}
|
|
227
227
|
className={cn(
|
|
228
228
|
rowClass,
|
|
229
|
-
"cursor-pointer outline-
|
|
229
|
+
"cursor-pointer outline-hidden hover:bg-og-surface-1 hover:text-og-fg",
|
|
230
230
|
"focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-0",
|
|
231
231
|
)}
|
|
232
232
|
>
|
|
@@ -498,7 +498,7 @@ export function Thumbnail({
|
|
|
498
498
|
type="button"
|
|
499
499
|
onClick={(event) => {
|
|
500
500
|
event.stopPropagation();
|
|
501
|
-
lightbox.open(src, caption);
|
|
501
|
+
lightbox.open(src, caption, event.currentTarget);
|
|
502
502
|
}}
|
|
503
503
|
onKeyDown={(event) => {
|
|
504
504
|
if (event.key === "Enter" || event.key === " ") {
|
|
@@ -507,7 +507,7 @@ export function Thumbnail({
|
|
|
507
507
|
}}
|
|
508
508
|
className={cn(
|
|
509
509
|
MEDIA_BOX,
|
|
510
|
-
"group/thumb relative inline-flex overflow-hidden bg-og-bg outline-
|
|
510
|
+
"group/thumb relative inline-flex overflow-hidden bg-og-bg outline-hidden",
|
|
511
511
|
"focus-visible:ring-2 focus-visible:ring-og-accent",
|
|
512
512
|
)}
|
|
513
513
|
aria-label="Expand screenshot"
|
|
@@ -554,7 +554,7 @@ export function ScreenshotFigure({
|
|
|
554
554
|
) : lightbox ? (
|
|
555
555
|
<button
|
|
556
556
|
type="button"
|
|
557
|
-
onClick={() => lightbox.open(src, caption)}
|
|
557
|
+
onClick={(event) => lightbox.open(src, caption, event.currentTarget)}
|
|
558
558
|
className={surface}
|
|
559
559
|
aria-label="Expand screenshot"
|
|
560
560
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GitFileDiff } from "@opengeni/sdk";
|
|
1
|
+
import type { GitFileDiff, RetainedArtifactReference } from "@opengeni/sdk";
|
|
2
2
|
import {
|
|
3
3
|
BoxIcon,
|
|
4
4
|
BrainCircuitIcon,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
TerminalIcon,
|
|
29
29
|
WrenchIcon,
|
|
30
30
|
} from "lucide-react";
|
|
31
|
-
import type
|
|
31
|
+
import { useEffect, useState, type ReactNode } from "react";
|
|
32
32
|
import { stringifyPayload, tryParseJson } from "../lib/format";
|
|
33
33
|
import { useTimelineComputeLabel } from "./compute-label";
|
|
34
34
|
import {
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
mediaPreviewFact,
|
|
41
41
|
parseExecBannerSessionId,
|
|
42
42
|
parseToolArgs,
|
|
43
|
-
|
|
43
|
+
retainedScreenshotMetadata,
|
|
44
44
|
sandboxCommandExitCode,
|
|
45
45
|
stripExecBanner,
|
|
46
46
|
tailPeek,
|
|
@@ -559,7 +559,7 @@ function asComputerArgs(args: unknown): Partial<ComputerAction> {
|
|
|
559
559
|
};
|
|
560
560
|
}
|
|
561
561
|
|
|
562
|
-
function ComputerCallRenderer({ item }: ToolRendererProps) {
|
|
562
|
+
function ComputerCallRenderer({ item, loadRetainedScreenshot }: ToolRendererProps) {
|
|
563
563
|
const raw = (item.raw ?? {}) as {
|
|
564
564
|
action?: ComputerAction;
|
|
565
565
|
actions?: ComputerAction[];
|
|
@@ -582,6 +582,7 @@ function ComputerCallRenderer({ item }: ToolRendererProps) {
|
|
|
582
582
|
const rejected = raw.providerData?.approvalStatus === "rejected";
|
|
583
583
|
const readOnly = typeof out === "string" && out.includes("read-only");
|
|
584
584
|
const shotUrl = screenshotDataUrl(out);
|
|
585
|
+
const retained = retainedScreenshotMetadata(out);
|
|
585
586
|
const omittedMedia = mediaPreviewFact(out);
|
|
586
587
|
const empty = out === "" || out == null;
|
|
587
588
|
const batched = actions.length > 1 ? actions.map((a) => computerVerb(a)).join(" · ") : null;
|
|
@@ -640,6 +641,41 @@ function ComputerCallRenderer({ item }: ToolRendererProps) {
|
|
|
640
641
|
const isFailed = item.status === "failed";
|
|
641
642
|
const isCancelled = item.status === "cancelled";
|
|
642
643
|
|
|
644
|
+
if (retained) {
|
|
645
|
+
if (!retained.available) {
|
|
646
|
+
const state =
|
|
647
|
+
retained.reason === "expired" || retained.reason === "deleted"
|
|
648
|
+
? retained.reason
|
|
649
|
+
: "unavailable";
|
|
650
|
+
return (
|
|
651
|
+
<ActivityDisclosure
|
|
652
|
+
icon={<CameraOffIcon className={ICON_SIZE} />}
|
|
653
|
+
iconTone={isFailed ? "failed" : "muted"}
|
|
654
|
+
title={`${verb}${countSuffix} · ${state}`}
|
|
655
|
+
failed={isFailed}
|
|
656
|
+
cancelled={isCancelled}
|
|
657
|
+
preview={`screenshot ${state}`}
|
|
658
|
+
media={<MediaEmpty />}
|
|
659
|
+
>
|
|
660
|
+
<BodyNote tone={isFailed ? "error" : undefined}>
|
|
661
|
+
Screenshot {state}: {retained.reason.replaceAll("_", " ")}.
|
|
662
|
+
</BodyNote>
|
|
663
|
+
</ActivityDisclosure>
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
return (
|
|
667
|
+
<RetainedScreenshotDisclosure
|
|
668
|
+
artifact={retained}
|
|
669
|
+
load={loadRetainedScreenshot}
|
|
670
|
+
verb={verb}
|
|
671
|
+
countSuffix={countSuffix}
|
|
672
|
+
batched={batched}
|
|
673
|
+
failed={isFailed}
|
|
674
|
+
cancelled={isCancelled}
|
|
675
|
+
/>
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
|
|
643
679
|
if (shotUrl) {
|
|
644
680
|
const caption = `${verb}${actions.length > 1 ? ` (+${actions.length - 1} more)` : ""}`;
|
|
645
681
|
return (
|
|
@@ -720,6 +756,112 @@ function ComputerCallRenderer({ item }: ToolRendererProps) {
|
|
|
720
756
|
);
|
|
721
757
|
}
|
|
722
758
|
|
|
759
|
+
function RetainedScreenshotDisclosure({
|
|
760
|
+
artifact,
|
|
761
|
+
load,
|
|
762
|
+
verb,
|
|
763
|
+
countSuffix,
|
|
764
|
+
batched,
|
|
765
|
+
failed,
|
|
766
|
+
cancelled,
|
|
767
|
+
}: {
|
|
768
|
+
artifact: RetainedArtifactReference;
|
|
769
|
+
load: ToolRendererProps["loadRetainedScreenshot"];
|
|
770
|
+
verb: string;
|
|
771
|
+
countSuffix: string;
|
|
772
|
+
batched: string | null;
|
|
773
|
+
failed: boolean;
|
|
774
|
+
cancelled: boolean;
|
|
775
|
+
}) {
|
|
776
|
+
const [state, setState] = useState<
|
|
777
|
+
| { kind: "loading" }
|
|
778
|
+
| { kind: "ready"; url: string }
|
|
779
|
+
| { kind: "unavailable"; label: string }
|
|
780
|
+
| { kind: "error"; message: string }
|
|
781
|
+
>({ kind: "loading" });
|
|
782
|
+
|
|
783
|
+
useEffect(() => {
|
|
784
|
+
if (!load) {
|
|
785
|
+
setState({ kind: "unavailable", label: "retrieval is not configured" });
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
const controller = new AbortController();
|
|
789
|
+
let objectUrl: string | null = null;
|
|
790
|
+
setState({ kind: "loading" });
|
|
791
|
+
void load(artifact, controller.signal)
|
|
792
|
+
.then((bytes) => {
|
|
793
|
+
if (controller.signal.aborted) return;
|
|
794
|
+
if (!bytes) {
|
|
795
|
+
setState({ kind: "unavailable", label: "bytes are unavailable" });
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
objectUrl = URL.createObjectURL(new Blob([Uint8Array.from(bytes)], { type: "image/png" }));
|
|
799
|
+
setState({ kind: "ready", url: objectUrl });
|
|
800
|
+
})
|
|
801
|
+
.catch((error: unknown) => {
|
|
802
|
+
if (controller.signal.aborted) return;
|
|
803
|
+
const status =
|
|
804
|
+
error && typeof error === "object" && "status" in error
|
|
805
|
+
? Number((error as { status?: unknown }).status)
|
|
806
|
+
: null;
|
|
807
|
+
setState(
|
|
808
|
+
status === 404
|
|
809
|
+
? { kind: "unavailable", label: "deleted" }
|
|
810
|
+
: status === 410
|
|
811
|
+
? { kind: "unavailable", label: "expired or unavailable" }
|
|
812
|
+
: {
|
|
813
|
+
kind: "error",
|
|
814
|
+
message: "retrieval failed",
|
|
815
|
+
},
|
|
816
|
+
);
|
|
817
|
+
});
|
|
818
|
+
return () => {
|
|
819
|
+
controller.abort();
|
|
820
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
821
|
+
};
|
|
822
|
+
}, [artifact, load]);
|
|
823
|
+
|
|
824
|
+
const caption = `${verb}${countSuffix}`;
|
|
825
|
+
return (
|
|
826
|
+
<ActivityDisclosure
|
|
827
|
+
icon={<CameraIcon className={ICON_SIZE} />}
|
|
828
|
+
iconTone={failed ? "failed" : state.kind === "ready" ? "accent" : "muted"}
|
|
829
|
+
title={caption}
|
|
830
|
+
failed={failed}
|
|
831
|
+
cancelled={cancelled}
|
|
832
|
+
preview={
|
|
833
|
+
state.kind === "loading"
|
|
834
|
+
? "loading retained screenshot…"
|
|
835
|
+
: state.kind === "error"
|
|
836
|
+
? "screenshot retrieval failed"
|
|
837
|
+
: state.kind === "unavailable"
|
|
838
|
+
? `screenshot ${state.label}`
|
|
839
|
+
: undefined
|
|
840
|
+
}
|
|
841
|
+
media={
|
|
842
|
+
state.kind === "ready" ? (
|
|
843
|
+
<Thumbnail src={state.url} caption={caption} />
|
|
844
|
+
) : state.kind === "loading" ? (
|
|
845
|
+
<MediaSkeleton />
|
|
846
|
+
) : (
|
|
847
|
+
<MediaEmpty />
|
|
848
|
+
)
|
|
849
|
+
}
|
|
850
|
+
>
|
|
851
|
+
{state.kind === "ready" ? (
|
|
852
|
+
<ScreenshotFigure src={state.url} caption={caption} />
|
|
853
|
+
) : state.kind === "loading" ? (
|
|
854
|
+
<BodyNote>Loading the retained screenshot…</BodyNote>
|
|
855
|
+
) : state.kind === "unavailable" ? (
|
|
856
|
+
<BodyNote>Screenshot {state.label}.</BodyNote>
|
|
857
|
+
) : (
|
|
858
|
+
<BodyNote tone="error">Screenshot retrieval failed: {state.message}</BodyNote>
|
|
859
|
+
)}
|
|
860
|
+
{batched ? <BodyNote>batched: {batched}</BodyNote> : null}
|
|
861
|
+
</ActivityDisclosure>
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
|
|
723
865
|
/* ---- web_search ------------------------------------------------------------ */
|
|
724
866
|
|
|
725
867
|
type WebSearchResult = { title: string; domain: string; snippet: string };
|
|
@@ -995,7 +1137,7 @@ function SecretSetRenderer({ item }: ToolRendererProps) {
|
|
|
995
1137
|
running
|
|
996
1138
|
preview={<RunningPreview>setting…</RunningPreview>}
|
|
997
1139
|
>
|
|
998
|
-
<PayloadBlock label="Arguments" value={
|
|
1140
|
+
<PayloadBlock label="Arguments" value={args} />
|
|
999
1141
|
</ActivityDisclosure>
|
|
1000
1142
|
);
|
|
1001
1143
|
}
|
|
@@ -1010,7 +1152,7 @@ function SecretSetRenderer({ item }: ToolRendererProps) {
|
|
|
1010
1152
|
failed
|
|
1011
1153
|
preview={errorText ?? "variable write failed"}
|
|
1012
1154
|
>
|
|
1013
|
-
<PayloadBlock label="Arguments" value={
|
|
1155
|
+
<PayloadBlock label="Arguments" value={args} />
|
|
1014
1156
|
{errorText ? (
|
|
1015
1157
|
<PayloadBlock label="Error" value={errorText} failed />
|
|
1016
1158
|
) : (
|
|
@@ -1026,10 +1168,12 @@ function SecretSetRenderer({ item }: ToolRendererProps) {
|
|
|
1026
1168
|
iconTone="muted"
|
|
1027
1169
|
title={`Set ${name}`}
|
|
1028
1170
|
cancelled={item.status === "cancelled"}
|
|
1029
|
-
preview="value
|
|
1171
|
+
preview="exact value preserved"
|
|
1030
1172
|
>
|
|
1031
|
-
<PayloadBlock label="Arguments" value={
|
|
1032
|
-
<BodyNote>
|
|
1173
|
+
<PayloadBlock label="Arguments" value={args} />
|
|
1174
|
+
<BodyNote>
|
|
1175
|
+
The configured value is preserved exactly and available through authorized secret reads.
|
|
1176
|
+
</BodyNote>
|
|
1033
1177
|
</ActivityDisclosure>
|
|
1034
1178
|
);
|
|
1035
1179
|
}
|
|
@@ -1175,7 +1319,7 @@ function ToolSearchRenderer({ item }: ToolRendererProps) {
|
|
|
1175
1319
|
}
|
|
1176
1320
|
>
|
|
1177
1321
|
{query ? <BodyNote>capability query: {query}</BodyNote> : null}
|
|
1178
|
-
<PayloadBlock label="Arguments" value={
|
|
1322
|
+
<PayloadBlock label="Arguments" value={parseToolArgs(item.arguments)} />
|
|
1179
1323
|
</ActivityDisclosure>
|
|
1180
1324
|
);
|
|
1181
1325
|
}
|
|
@@ -1191,7 +1335,7 @@ function ToolSearchRenderer({ item }: ToolRendererProps) {
|
|
|
1191
1335
|
preview={truncatePreview(outText, 80) || queryPreview || "Lookup failed"}
|
|
1192
1336
|
>
|
|
1193
1337
|
{query ? <BodyNote>capability query: {query}</BodyNote> : null}
|
|
1194
|
-
<PayloadBlock label="Arguments" value={
|
|
1338
|
+
<PayloadBlock label="Arguments" value={parseToolArgs(item.arguments)} />
|
|
1195
1339
|
<PayloadBlock label="Error" value={outText} failed />
|
|
1196
1340
|
</ActivityDisclosure>
|
|
1197
1341
|
);
|
|
@@ -1226,7 +1370,7 @@ function ToolSearchRenderer({ item }: ToolRendererProps) {
|
|
|
1226
1370
|
) : tools && tools.length === 0 ? (
|
|
1227
1371
|
<BodyNote>no deferred tools matched this capability query.</BodyNote>
|
|
1228
1372
|
) : null}
|
|
1229
|
-
<PayloadBlock label="Arguments" value={
|
|
1373
|
+
<PayloadBlock label="Arguments" value={parseToolArgs(item.arguments)} />
|
|
1230
1374
|
{tools == null && outText ? <PayloadBlock label="Result" value={outText} /> : null}
|
|
1231
1375
|
</ActivityDisclosure>
|
|
1232
1376
|
);
|
|
@@ -1249,7 +1393,7 @@ function DocsSearchRenderer({ item }: ToolRendererProps) {
|
|
|
1249
1393
|
running
|
|
1250
1394
|
preview={<RunningPreview>Searching…</RunningPreview>}
|
|
1251
1395
|
>
|
|
1252
|
-
<PayloadBlock label="Arguments" value={
|
|
1396
|
+
<PayloadBlock label="Arguments" value={args} />
|
|
1253
1397
|
</ActivityDisclosure>
|
|
1254
1398
|
);
|
|
1255
1399
|
}
|
|
@@ -1264,7 +1408,7 @@ function DocsSearchRenderer({ item }: ToolRendererProps) {
|
|
|
1264
1408
|
failed
|
|
1265
1409
|
preview={truncatePreview(outText, 80) || "Search failed"}
|
|
1266
1410
|
>
|
|
1267
|
-
<PayloadBlock label="Arguments" value={
|
|
1411
|
+
<PayloadBlock label="Arguments" value={args} />
|
|
1268
1412
|
<PayloadBlock label="Error" value={outText} failed />
|
|
1269
1413
|
</ActivityDisclosure>
|
|
1270
1414
|
);
|
|
@@ -1300,7 +1444,7 @@ function DocsSearchRenderer({ item }: ToolRendererProps) {
|
|
|
1300
1444
|
))}
|
|
1301
1445
|
</ul>
|
|
1302
1446
|
) : null}
|
|
1303
|
-
<PayloadBlock label="Arguments" value={
|
|
1447
|
+
<PayloadBlock label="Arguments" value={args} />
|
|
1304
1448
|
<PayloadBlock label="Result" value={outText} />
|
|
1305
1449
|
</ActivityDisclosure>
|
|
1306
1450
|
);
|
|
@@ -1330,7 +1474,7 @@ function SetSessionTitleRenderer({ item }: ToolRendererProps) {
|
|
|
1330
1474
|
)
|
|
1331
1475
|
}
|
|
1332
1476
|
>
|
|
1333
|
-
<PayloadBlock label="Arguments" value={
|
|
1477
|
+
<PayloadBlock label="Arguments" value={args} />
|
|
1334
1478
|
</ActivityDisclosure>
|
|
1335
1479
|
);
|
|
1336
1480
|
}
|
|
@@ -1345,7 +1489,7 @@ function SetSessionTitleRenderer({ item }: ToolRendererProps) {
|
|
|
1345
1489
|
failed
|
|
1346
1490
|
preview={truncatePreview(outText, 80) || "Rename failed"}
|
|
1347
1491
|
>
|
|
1348
|
-
<PayloadBlock label="Arguments" value={
|
|
1492
|
+
<PayloadBlock label="Arguments" value={args} />
|
|
1349
1493
|
<PayloadBlock label="Error" value={outText} failed />
|
|
1350
1494
|
</ActivityDisclosure>
|
|
1351
1495
|
);
|
|
@@ -1371,7 +1515,7 @@ function SetSessionTitleRenderer({ item }: ToolRendererProps) {
|
|
|
1371
1515
|
cancelled={item.status === "cancelled"}
|
|
1372
1516
|
preview={item.status === "cancelled" ? undefined : settledTitle || undefined}
|
|
1373
1517
|
>
|
|
1374
|
-
<PayloadBlock label="Arguments" value={
|
|
1518
|
+
<PayloadBlock label="Arguments" value={args} />
|
|
1375
1519
|
{outText ? <PayloadBlock label="Result" value={outText} /> : null}
|
|
1376
1520
|
</ActivityDisclosure>
|
|
1377
1521
|
);
|
|
@@ -1431,7 +1575,7 @@ function MemoryProposeRenderer({ item }: ToolRendererProps) {
|
|
|
1431
1575
|
running
|
|
1432
1576
|
preview={<RunningPreview>Proposing…</RunningPreview>}
|
|
1433
1577
|
>
|
|
1434
|
-
<PayloadBlock label="Arguments" value={
|
|
1578
|
+
<PayloadBlock label="Arguments" value={args} />
|
|
1435
1579
|
</ActivityDisclosure>
|
|
1436
1580
|
);
|
|
1437
1581
|
}
|
|
@@ -1493,7 +1637,7 @@ function askToolPreview(args: unknown): string | null {
|
|
|
1493
1637
|
}
|
|
1494
1638
|
|
|
1495
1639
|
function AskRenderer({ item }: ToolRendererProps) {
|
|
1496
|
-
const args =
|
|
1640
|
+
const args = parseToolArgs(item.arguments);
|
|
1497
1641
|
const preview = askToolPreview(args);
|
|
1498
1642
|
const icon = <MessageCircleQuestionIcon className={ICON_SIZE} />;
|
|
1499
1643
|
const title = "Ask";
|
|
@@ -1583,7 +1727,7 @@ function runOnOpPreview(args: Record<string, unknown>): string | null {
|
|
|
1583
1727
|
|
|
1584
1728
|
function RunOnRenderer({ item }: ToolRendererProps) {
|
|
1585
1729
|
const parsedArgs = parseToolArgs(item.arguments);
|
|
1586
|
-
const args =
|
|
1730
|
+
const args = parsedArgs;
|
|
1587
1731
|
const targetName = runOnTargetName(item.output);
|
|
1588
1732
|
const title = targetName ? `Run on ${targetName}` : "Run on";
|
|
1589
1733
|
const opPreview = runOnOpPreview(parsedArgs);
|
|
@@ -1648,7 +1792,7 @@ function RunOnRenderer({ item }: ToolRendererProps) {
|
|
|
1648
1792
|
*/
|
|
1649
1793
|
function GenericRenderer({ item }: ToolRendererProps) {
|
|
1650
1794
|
const running = item.status === "running";
|
|
1651
|
-
const args =
|
|
1795
|
+
const args = parseToolArgs(item.arguments);
|
|
1652
1796
|
const display = toolDisplayName(item.name);
|
|
1653
1797
|
const icon = <GenericToolIcon name={item.name} />;
|
|
1654
1798
|
// Goal tools: surface the objective text on the collapsed row so the in-cluster
|
|
@@ -416,8 +416,8 @@ export function TurnSummary({
|
|
|
416
416
|
// as a turn landmark above any nested cluster rows it groups.
|
|
417
417
|
"group flex w-full items-center rounded-og-sm text-left transition-colors",
|
|
418
418
|
// A folded turn is a touch target on coarse pointers: grow the row so it
|
|
419
|
-
// clears the
|
|
420
|
-
"pointer-coarse:py-2.5",
|
|
419
|
+
// clears the 44px minimum without disturbing the calm desktop rhythm.
|
|
420
|
+
"pointer-coarse:min-h-11 pointer-coarse:py-2.5",
|
|
421
421
|
bare
|
|
422
422
|
? "gap-2 px-1.5 py-1.5 text-og-sm text-og-fg-muted"
|
|
423
423
|
: "-mx-2 gap-2.5 px-2 py-1.5 text-og-base text-og-fg-muted",
|
|
@@ -437,8 +437,8 @@ export function TurnSummary({
|
|
|
437
437
|
className={cn(
|
|
438
438
|
"size-3.5 shrink-0 text-og-fg-subtle transition-transform ease-og-in-out group-data-[state=open]:rotate-90",
|
|
439
439
|
settlePhase
|
|
440
|
-
? "duration-[var(--
|
|
441
|
-
: "duration-[var(--
|
|
440
|
+
? "duration-[var(--_og-duration-disclose-settle)]"
|
|
441
|
+
: "duration-[var(--_og-duration-disclose)]",
|
|
442
442
|
)}
|
|
443
443
|
/>
|
|
444
444
|
{/* Completion is the quiet default and needs no repeated glyph. Failed,
|
|
@@ -646,8 +646,8 @@ const BUILT_IN_TURN_SUMMARY_FACETS: readonly TurnSummaryFacet[] = Object.freeze(
|
|
|
646
646
|
contextCompactionCount === 1 ? "compacted" : `${contextCompactionCount} compacts`,
|
|
647
647
|
ariaLabel:
|
|
648
648
|
contextCompactionCount === 1
|
|
649
|
-
? "Conversation
|
|
650
|
-
: `${contextCompactionCount} conversation
|
|
649
|
+
? "Conversation history compacted"
|
|
650
|
+
: `${contextCompactionCount} conversation history compactions`,
|
|
651
651
|
}
|
|
652
652
|
: null,
|
|
653
653
|
},
|
package/src/timeline/types.ts
CHANGED
|
@@ -288,6 +288,10 @@ export type AuthNeededItem = {
|
|
|
288
288
|
kind: "auth-needed";
|
|
289
289
|
id: string;
|
|
290
290
|
turnId: string | null;
|
|
291
|
+
/** The runtime surface that requested recovery, when the event is an MCP auth signal. */
|
|
292
|
+
serverId: string | null;
|
|
293
|
+
/** Durable event family that produced this notice. */
|
|
294
|
+
source?: "tool" | "credential" | undefined;
|
|
291
295
|
/** The connection's registrable domain, e.g. "linear.app". */
|
|
292
296
|
providerDomain: string;
|
|
293
297
|
/** The lapsed connection to reconnect, when the row survived. */
|
|
@@ -9,6 +9,8 @@ export type VoiceRecordingUploadState = "pending" | "syncing" | "retrying" | "co
|
|
|
9
9
|
export type VoiceRecordingTranscriptionState = "pending" | "transcribing" | "retrying" | "complete";
|
|
10
10
|
export type VoiceRecordingFinalizationState = "pending" | "transcript-ready" | "handed-off";
|
|
11
11
|
export type VoiceRecordingChunkUploadState = "pending" | "syncing" | "complete";
|
|
12
|
+
export type VoiceRecordingRecoveryMode = "automatic" | "manual";
|
|
13
|
+
export type VoiceRecordingHandoffMode = "append" | "explicit";
|
|
12
14
|
|
|
13
15
|
export type VoiceRecordingManifest = {
|
|
14
16
|
version: 1;
|
|
@@ -22,6 +24,10 @@ export type VoiceRecordingManifest = {
|
|
|
22
24
|
uploadState: VoiceRecordingUploadState;
|
|
23
25
|
transcriptionState: VoiceRecordingTranscriptionState;
|
|
24
26
|
finalizationState: VoiceRecordingFinalizationState;
|
|
27
|
+
/** Whether safe server reconciliation should continue without another user action. */
|
|
28
|
+
recoveryMode: VoiceRecordingRecoveryMode;
|
|
29
|
+
/** Delayed/recovered results require explicit insertion instead of mutating an uncertain draft. */
|
|
30
|
+
handoffMode: VoiceRecordingHandoffMode;
|
|
25
31
|
/** Tab/process lease. A different owner may take over only after this heartbeat is stale. */
|
|
26
32
|
ownerId: string | null;
|
|
27
33
|
ownerHeartbeatAt: string | null;
|
|
@@ -91,6 +97,8 @@ export interface VoiceRecordingStore {
|
|
|
91
97
|
| "ownerId"
|
|
92
98
|
| "ownerHeartbeatAt"
|
|
93
99
|
| "transcriptText"
|
|
100
|
+
| "recoveryMode"
|
|
101
|
+
| "handoffMode"
|
|
94
102
|
>
|
|
95
103
|
>,
|
|
96
104
|
updatedAt: string,
|
|
@@ -155,6 +163,8 @@ export function createVoiceRecordingManifest(input: {
|
|
|
155
163
|
uploadState: "pending",
|
|
156
164
|
transcriptionState: "pending",
|
|
157
165
|
finalizationState: "pending",
|
|
166
|
+
recoveryMode: "automatic",
|
|
167
|
+
handoffMode: "append",
|
|
158
168
|
ownerId: input.ownerId ?? null,
|
|
159
169
|
ownerHeartbeatAt: input.ownerId ? input.createdAt : null,
|
|
160
170
|
transcriptText: null,
|
|
@@ -306,6 +316,7 @@ export class IndexedDbVoiceRecordingStore implements VoiceRecordingStore {
|
|
|
306
316
|
const updated: VoiceRecordingManifest = {
|
|
307
317
|
...manifest,
|
|
308
318
|
captureState: manifest.captureState === "capturing" ? "stopped" : manifest.captureState,
|
|
319
|
+
handoffMode: "explicit",
|
|
309
320
|
ownerId,
|
|
310
321
|
ownerHeartbeatAt: claimedAt,
|
|
311
322
|
updatedAt: claimedAt,
|
|
@@ -370,6 +381,8 @@ export class IndexedDbVoiceRecordingStore implements VoiceRecordingStore {
|
|
|
370
381
|
| "ownerId"
|
|
371
382
|
| "ownerHeartbeatAt"
|
|
372
383
|
| "transcriptText"
|
|
384
|
+
| "recoveryMode"
|
|
385
|
+
| "handoffMode"
|
|
373
386
|
>
|
|
374
387
|
>,
|
|
375
388
|
updatedAt: string,
|
|
@@ -455,6 +468,8 @@ function normalizeManifest(manifest: VoiceRecordingManifest): VoiceRecordingMani
|
|
|
455
468
|
ownerId: manifest.ownerId ?? null,
|
|
456
469
|
ownerHeartbeatAt: manifest.ownerHeartbeatAt ?? null,
|
|
457
470
|
transcriptText: manifest.transcriptText ?? null,
|
|
471
|
+
recoveryMode: manifest.recoveryMode ?? "manual",
|
|
472
|
+
handoffMode: manifest.handoffMode ?? "explicit",
|
|
458
473
|
};
|
|
459
474
|
}
|
|
460
475
|
|