@opengeni/react 7.6.1 → 7.7.0-canary.37111190284001
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 +47 -2
- package/dist/{browser-viewer-5X47I6T2.js → browser-viewer-63TXMINB.js} +6 -5
- package/dist/{chunk-5EXGU7UF.js → chunk-55DDDVKU.js} +3 -3
- package/dist/{chunk-UINDK6C3.js → chunk-7CYYXWHU.js} +5 -8
- package/dist/chunk-7CYYXWHU.js.map +1 -0
- package/dist/{chunk-LZ3KHA55.js → chunk-A2L5N3EI.js} +332 -339
- package/dist/chunk-A2L5N3EI.js.map +1 -0
- package/dist/{chunk-KGENSZKS.js → chunk-AAG4R3QN.js} +4 -3
- package/dist/chunk-AAG4R3QN.js.map +1 -0
- package/dist/{chunk-Z3DISEQN.js → chunk-CWWKTNKK.js} +35 -18
- package/dist/chunk-CWWKTNKK.js.map +1 -0
- package/dist/{chunk-WZ55DVGK.js → chunk-ELCE5SXV.js} +8 -4
- package/dist/chunk-ELCE5SXV.js.map +1 -0
- package/dist/{chunk-YSMMDOOE.js → chunk-GISEVGS6.js} +10 -2
- package/dist/chunk-GISEVGS6.js.map +1 -0
- package/dist/{chunk-BI57CQUV.js → chunk-I7IWRQLN.js} +2 -2
- package/dist/{chunk-XA6R7DJQ.js → chunk-IQUCRAT7.js} +3 -3
- package/dist/{chunk-CHPKSLPN.js → chunk-NBZ64YY7.js} +4 -2
- package/dist/chunk-NBZ64YY7.js.map +1 -0
- package/dist/{chunk-DV2DBNPQ.js → chunk-Q5QBEZIQ.js} +48 -29
- package/dist/chunk-Q5QBEZIQ.js.map +1 -0
- package/dist/{chunk-FGBCYQOT.js → chunk-RDKBXMWX.js} +120 -74
- package/dist/chunk-RDKBXMWX.js.map +1 -0
- package/dist/{chunk-45GE35O7.js → chunk-TKB4SB44.js} +10 -2
- package/dist/chunk-TKB4SB44.js.map +1 -0
- package/dist/{chunk-YCGBREUV.js → chunk-TOAH2BKA.js} +6 -2
- package/dist/{chunk-YCGBREUV.js.map → chunk-TOAH2BKA.js.map} +1 -1
- package/dist/chunk-UGLEYEMP.js +33 -0
- package/dist/chunk-UGLEYEMP.js.map +1 -0
- package/dist/{chunk-UJRYEWZI.js → chunk-UTRTG6LH.js} +48 -8
- package/dist/chunk-UTRTG6LH.js.map +1 -0
- package/dist/{chunk-YRYF2GLG.js → chunk-WYVLAAJA.js} +39 -31
- package/dist/chunk-WYVLAAJA.js.map +1 -0
- package/dist/{chunk-VGIKFIO2.js → chunk-XOAMRDDK.js} +6 -3
- package/dist/chunk-XOAMRDDK.js.map +1 -0
- package/dist/{chunk-6NEZPF53.js → chunk-YN7I3STW.js} +153 -32
- package/dist/chunk-YN7I3STW.js.map +1 -0
- package/dist/{chunk-KZ73RL76.js → chunk-YQEAA6LT.js} +7 -3
- package/dist/chunk-YQEAA6LT.js.map +1 -0
- package/dist/client.d.ts +4 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/approval-surface.d.ts +15 -0
- package/dist/components/session-conversation.d.ts +3 -0
- package/dist/composer.js +8 -7
- package/dist/{computer-viewer-7GF35SNY.js → computer-viewer-X2MBRZY4.js} +5 -4
- package/dist/connect.js +3 -3
- package/dist/hooks/use-browser-session.d.ts +4 -0
- package/dist/hooks/use-composer.d.ts +2 -0
- package/dist/hooks/use-computer-session.d.ts +2 -0
- package/dist/hooks/use-file-attachments.d.ts +2 -0
- package/dist/hooks/use-session-events.d.ts +1 -1
- package/dist/hooks/use-workspace-sessions.d.ts +15 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +28 -21
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.js +6 -5
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-3BJGKQQ6.js → latest-question-CNEZ5KAN.js} +3 -3
- package/dist/lib/error-message.d.ts +7 -0
- package/dist/lib/format.d.ts +27 -3
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/machines.js +5 -4
- package/dist/{model-policy-picker-menu-PUTA5JOB.js → model-policy-picker-menu-GN2LTB7Z.js} +4 -4
- package/dist/model-policy.js +1 -1
- package/dist/provider.d.ts +5 -1
- package/dist/{queue-surface-implementation-2CAQAIHM.js → queue-surface-implementation-HENWYFWA.js} +3 -2
- package/dist/{queue-surface-implementation-2CAQAIHM.js.map → queue-surface-implementation-HENWYFWA.js.map} +1 -1
- package/dist/realtime.js +2 -2
- package/dist/{sandbox-files-AEJTWGT4.js → sandbox-files-BBC47SLD.js} +4 -3
- package/dist/session-ui.d.ts +1 -1
- package/dist/session-ui.js +13 -12
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +7 -6
- package/dist/usage.js +2 -1
- package/dist/usage.js.map +1 -1
- package/package.json +3 -3
- package/src/client.ts +5 -1
- package/src/commands/registry.ts +18 -15
- package/src/commands/types.ts +2 -0
- package/src/components/approval-surface.tsx +166 -42
- package/src/components/browser-viewer.tsx +38 -9
- package/src/components/composer.tsx +9 -2
- package/src/components/computer-viewer.tsx +160 -30
- package/src/components/generated-video-player.tsx +4 -2
- package/src/components/human-input-form.tsx +18 -5
- package/src/components/message-timeline.tsx +120 -168
- package/src/components/model-policy-picker.tsx +2 -15
- package/src/components/open-geni-chat.tsx +11 -4
- package/src/components/queue-surface-state.tsx +3 -1
- package/src/components/session-chrome.tsx +5 -2
- package/src/components/session-commands-panel.tsx +3 -1
- package/src/components/session-conversation.tsx +10 -3
- package/src/components/session-list.tsx +7 -5
- package/src/components/session-mcp-capability-card.tsx +11 -1
- package/src/hooks/internal.ts +3 -2
- package/src/hooks/use-browser-session.ts +115 -79
- package/src/hooks/use-composer.ts +70 -31
- package/src/hooks/use-computer-session.ts +57 -6
- package/src/hooks/use-file-attachments.ts +16 -3
- package/src/hooks/use-goal.ts +2 -1
- package/src/hooks/use-session-capabilities.ts +3 -2
- package/src/hooks/use-session-events.ts +15 -8
- package/src/hooks/use-slash-commands.ts +6 -11
- package/src/hooks/use-turn-queue.ts +2 -1
- package/src/hooks/use-workspace-sessions.ts +40 -7
- package/src/index.ts +7 -2
- package/src/interaction.ts +1 -0
- package/src/lib/error-message.ts +37 -0
- package/src/lib/format.ts +73 -9
- package/src/lib/interaction-errors.ts +15 -0
- package/src/model-policy.ts +6 -1
- package/src/provider.tsx +12 -4
- package/src/session-ui.ts +1 -1
- package/src/timeline/projection.ts +12 -1
- package/styles/compiled.css +14 -30
- package/dist/chunk-45GE35O7.js.map +0 -1
- package/dist/chunk-6NEZPF53.js.map +0 -1
- package/dist/chunk-CHPKSLPN.js.map +0 -1
- package/dist/chunk-DV2DBNPQ.js.map +0 -1
- package/dist/chunk-FGBCYQOT.js.map +0 -1
- package/dist/chunk-KGENSZKS.js.map +0 -1
- package/dist/chunk-KZ73RL76.js.map +0 -1
- package/dist/chunk-LZ3KHA55.js.map +0 -1
- package/dist/chunk-UINDK6C3.js.map +0 -1
- package/dist/chunk-UJRYEWZI.js.map +0 -1
- package/dist/chunk-VGIKFIO2.js.map +0 -1
- package/dist/chunk-WZ55DVGK.js.map +0 -1
- package/dist/chunk-YRYF2GLG.js.map +0 -1
- package/dist/chunk-YSMMDOOE.js.map +0 -1
- package/dist/chunk-Z3DISEQN.js.map +0 -1
- package/dist/components/timeline-pill-geometry.d.ts +0 -8
- package/dist/components/timeline-pill-placement.d.ts +0 -24
- package/dist/timeline-pill-geometry-Z23V243M.js +0 -110
- package/dist/timeline-pill-geometry-Z23V243M.js.map +0 -1
- package/src/components/timeline-pill-geometry.ts +0 -169
- package/src/components/timeline-pill-placement.ts +0 -99
- /package/dist/{browser-viewer-5X47I6T2.js.map → browser-viewer-63TXMINB.js.map} +0 -0
- /package/dist/{chunk-5EXGU7UF.js.map → chunk-55DDDVKU.js.map} +0 -0
- /package/dist/{chunk-BI57CQUV.js.map → chunk-I7IWRQLN.js.map} +0 -0
- /package/dist/{chunk-XA6R7DJQ.js.map → chunk-IQUCRAT7.js.map} +0 -0
- /package/dist/{computer-viewer-7GF35SNY.js.map → computer-viewer-X2MBRZY4.js.map} +0 -0
- /package/dist/{latest-question-3BJGKQQ6.js.map → latest-question-CNEZ5KAN.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-PUTA5JOB.js.map → model-policy-picker-menu-GN2LTB7Z.js.map} +0 -0
- /package/dist/{sandbox-files-AEJTWGT4.js.map → sandbox-files-BBC47SLD.js.map} +0 -0
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import { CheckIcon, ShieldCheckIcon, XIcon } from "lucide-react";
|
|
2
|
-
import { useEffect, useId, useRef, useState, type ReactNode } from "react";
|
|
2
|
+
import { Fragment, useEffect, useId, useRef, useState, type ReactNode } from "react";
|
|
3
3
|
import type { PendingApproval } from "../approvals";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
5
6
|
import { toolDisplayName } from "../timeline/tool-display-name";
|
|
6
7
|
|
|
7
8
|
export type ApprovalSurfaceMessages = {
|
|
8
9
|
title: string;
|
|
10
|
+
/** Screen-reader context for the decision; not shown visually. */
|
|
9
11
|
description: string;
|
|
10
12
|
approve: string;
|
|
11
13
|
reject: string;
|
|
12
14
|
approving: string;
|
|
13
15
|
rejecting: string;
|
|
14
16
|
formatToolName: (name: string) => string;
|
|
17
|
+
/** Toggle for the exact arguments when they are shown as readable fields. */
|
|
18
|
+
showDetails?: string | undefined;
|
|
19
|
+
hideDetails?: string | undefined;
|
|
15
20
|
};
|
|
16
21
|
|
|
17
22
|
export const defaultApprovalSurfaceMessages: ApprovalSurfaceMessages = {
|
|
18
|
-
title: "Approval
|
|
23
|
+
title: "Approval needed",
|
|
19
24
|
description: "Review the requested action before the agent continues.",
|
|
20
25
|
approve: "Approve",
|
|
21
26
|
reject: "Reject",
|
|
@@ -25,6 +30,8 @@ export const defaultApprovalSurfaceMessages: ApprovalSurfaceMessages = {
|
|
|
25
30
|
name.includes("__") || /^[a-f0-9]{64}$/.test(name)
|
|
26
31
|
? toolDisplayName(name)
|
|
27
32
|
: name.replaceAll("_", " ").replaceAll(".", " › "),
|
|
33
|
+
showDetails: "Show exact arguments",
|
|
34
|
+
hideDetails: "Hide exact arguments",
|
|
28
35
|
};
|
|
29
36
|
|
|
30
37
|
export type ApprovalSurfaceProps = {
|
|
@@ -39,6 +46,8 @@ export type ApprovalSurfaceProps = {
|
|
|
39
46
|
};
|
|
40
47
|
|
|
41
48
|
const APPROVAL_ARGUMENT_PREVIEW_CHARACTERS = 4_000;
|
|
49
|
+
const APPROVAL_FIELD_LIMIT = 12;
|
|
50
|
+
const APPROVAL_LIST_LIMIT = 10;
|
|
42
51
|
|
|
43
52
|
function approvalArgumentsPreview(value: unknown): string | null {
|
|
44
53
|
if (value === undefined) return null;
|
|
@@ -54,6 +63,68 @@ function approvalArgumentsPreview(value: unknown): string | null {
|
|
|
54
63
|
return `${characters.slice(0, APPROVAL_ARGUMENT_PREVIEW_CHARACTERS).join("")}\n… ${characters.length - APPROVAL_ARGUMENT_PREVIEW_CHARACTERS} characters omitted`;
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
export type ApprovalField = { key: string; label: string; value: string };
|
|
67
|
+
|
|
68
|
+
const ACRONYMS = new Set(["id", "ids", "url", "uri", "api", "ip", "sku", "utc", "iban", "vat"]);
|
|
69
|
+
|
|
70
|
+
/** "paymentId" / "payment_id" → "Payment ID". */
|
|
71
|
+
function fieldLabel(key: string): string {
|
|
72
|
+
const words = key
|
|
73
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
74
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
75
|
+
.split(/[\s_.-]+/)
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
.map((word) => (ACRONYMS.has(word.toLowerCase()) ? word.toUpperCase() : word.toLowerCase()));
|
|
78
|
+
const label = words.join(" ");
|
|
79
|
+
return label ? label.charAt(0).toUpperCase() + label.slice(1) : key;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function scalarText(value: unknown): string | null {
|
|
83
|
+
if (typeof value === "string") return value;
|
|
84
|
+
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
85
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
86
|
+
if (value === null) return "None";
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Readable fields for a flat argument object (scalars and short scalar lists).
|
|
92
|
+
* Returns null when the arguments are nested, large, or not an object, so the
|
|
93
|
+
* caller shows the exact JSON instead of a lossy summary.
|
|
94
|
+
*/
|
|
95
|
+
export function approvalArgumentFields(value: unknown): ApprovalField[] | null {
|
|
96
|
+
let record = value;
|
|
97
|
+
if (typeof record === "string") {
|
|
98
|
+
try {
|
|
99
|
+
record = JSON.parse(record) as unknown;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) return null;
|
|
105
|
+
const entries = Object.entries(record as Record<string, unknown>);
|
|
106
|
+
if (entries.length === 0 || entries.length > APPROVAL_FIELD_LIMIT) return null;
|
|
107
|
+
const fields: ApprovalField[] = [];
|
|
108
|
+
for (const [key, entry] of entries) {
|
|
109
|
+
let text = scalarText(entry);
|
|
110
|
+
if (text === null && Array.isArray(entry) && entry.length <= APPROVAL_LIST_LIMIT) {
|
|
111
|
+
const items = entry.map(scalarText);
|
|
112
|
+
if (items.every((item): item is string => item !== null)) text = items.join(", ");
|
|
113
|
+
}
|
|
114
|
+
if (text === null || text.length > APPROVAL_ARGUMENT_PREVIEW_CHARACTERS) return null;
|
|
115
|
+
fields.push({ key, label: fieldLabel(key), value: text });
|
|
116
|
+
}
|
|
117
|
+
return fields;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isEmptyArguments(value: unknown): boolean {
|
|
121
|
+
return (
|
|
122
|
+
value === undefined ||
|
|
123
|
+
value === null ||
|
|
124
|
+
(typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
57
128
|
function approvalOwnershipKey(approval: PendingApproval): string {
|
|
58
129
|
return `${approval.id}\u0000${approval.name}\u0000${approvalArgumentsPreview(approval.arguments) ?? ""}`;
|
|
59
130
|
}
|
|
@@ -74,6 +145,8 @@ export function ApprovalSurface({
|
|
|
74
145
|
className,
|
|
75
146
|
}: ApprovalSurfaceProps) {
|
|
76
147
|
const titleId = useId();
|
|
148
|
+
const formatError = useErrorMessage();
|
|
149
|
+
const descriptionId = useId();
|
|
77
150
|
const messages = { ...defaultApprovalSurfaceMessages, ...overrides };
|
|
78
151
|
const [pending, setPending] = useState<{
|
|
79
152
|
approvalKey: string;
|
|
@@ -81,7 +154,7 @@ export function ApprovalSurface({
|
|
|
81
154
|
token: symbol;
|
|
82
155
|
} | null>(null);
|
|
83
156
|
const pendingRef = useRef<{ approvalKey: string; token: symbol } | null>(null);
|
|
84
|
-
const [decisionError, setDecisionError] = useState<
|
|
157
|
+
const [decisionError, setDecisionError] = useState<{ cause: unknown } | null>(null);
|
|
85
158
|
|
|
86
159
|
useEffect(() => {
|
|
87
160
|
if (
|
|
@@ -116,69 +189,58 @@ export function ApprovalSurface({
|
|
|
116
189
|
if (pendingRef.current?.token === token) {
|
|
117
190
|
pendingRef.current = null;
|
|
118
191
|
setPending((current) => (current?.token === token ? null : current));
|
|
119
|
-
setDecisionError(
|
|
192
|
+
setDecisionError({ cause });
|
|
120
193
|
}
|
|
121
194
|
}
|
|
122
195
|
};
|
|
123
|
-
const errorMessage = decisionError
|
|
196
|
+
const errorMessage = decisionError
|
|
197
|
+
? formatError(decisionError.cause)
|
|
198
|
+
: error instanceof Error
|
|
199
|
+
? formatError(error)
|
|
200
|
+
: error;
|
|
124
201
|
|
|
125
202
|
return (
|
|
126
203
|
<section
|
|
127
204
|
className={cn(
|
|
128
|
-
"og-root flex w-full flex-col
|
|
205
|
+
"og-root box-border flex w-full flex-col rounded-og-lg border border-og-status-waiting/35 bg-og-status-waiting/5 shadow-og-sm",
|
|
129
206
|
className,
|
|
130
207
|
)}
|
|
131
208
|
aria-labelledby={titleId}
|
|
209
|
+
aria-describedby={descriptionId}
|
|
210
|
+
data-og-approval-surface=""
|
|
132
211
|
>
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
</h2>
|
|
141
|
-
<p className="mt-0.5 text-og-sm text-og-fg-muted">{messages.description}</p>
|
|
142
|
-
</div>
|
|
143
|
-
</header>
|
|
144
|
-
|
|
145
|
-
<div className="flex flex-col gap-2">
|
|
212
|
+
<h2 id={titleId} className="sr-only">
|
|
213
|
+
{messages.title}
|
|
214
|
+
</h2>
|
|
215
|
+
<p id={descriptionId} className="sr-only">
|
|
216
|
+
{messages.description}
|
|
217
|
+
</p>
|
|
218
|
+
<div className="flex flex-col divide-y divide-og-status-waiting/20">
|
|
146
219
|
{approvals.map((approval) => {
|
|
147
220
|
const approvalKey = approvalOwnershipKey(approval);
|
|
148
221
|
const active = pending?.approvalKey === approvalKey ? pending.decision : null;
|
|
149
|
-
const argumentsPreview = approvalArgumentsPreview(approval.arguments);
|
|
150
222
|
return (
|
|
151
223
|
<article
|
|
152
224
|
key={approval.id}
|
|
153
225
|
data-approval-id={approval.id}
|
|
154
|
-
className="
|
|
226
|
+
className="flex flex-col gap-3 p-4"
|
|
155
227
|
>
|
|
156
228
|
{renderApproval ? (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
<p className="text-og-sm font-medium text-og-fg">
|
|
161
|
-
{approval.display?.title ??
|
|
162
|
-
messages.formatToolName(approval.display?.toolName ?? approval.name)}
|
|
229
|
+
<div className="min-w-0">
|
|
230
|
+
<p className="mb-1 text-og-xs font-medium text-og-status-waiting" aria-hidden>
|
|
231
|
+
{messages.title}
|
|
163
232
|
</p>
|
|
164
|
-
{approval
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
) : null}
|
|
169
|
-
{argumentsPreview !== null ? (
|
|
170
|
-
<pre className="mt-2 max-h-48 overflow-auto whitespace-pre-wrap break-words rounded-og-sm bg-og-surface-2 p-2 font-mono text-og-xs text-og-fg-muted">
|
|
171
|
-
{argumentsPreview}
|
|
172
|
-
</pre>
|
|
173
|
-
) : null}
|
|
174
|
-
</>
|
|
233
|
+
{renderApproval(approval)}
|
|
234
|
+
</div>
|
|
235
|
+
) : (
|
|
236
|
+
<ApprovalDetails approval={approval} messages={messages} />
|
|
175
237
|
)}
|
|
176
|
-
<div className="
|
|
238
|
+
<div className="flex flex-wrap items-center justify-end gap-2">
|
|
177
239
|
<button
|
|
178
240
|
type="button"
|
|
179
241
|
disabled={busy}
|
|
180
242
|
onClick={() => void decide(approval, "reject")}
|
|
181
|
-
className="inline-flex min-h-
|
|
243
|
+
className="inline-flex min-h-8 items-center gap-1.5 rounded-og-md border border-og-border bg-og-surface-1 px-3 py-1 text-og-sm font-medium text-og-fg transition-colors hover:border-og-border-strong disabled:opacity-50"
|
|
182
244
|
>
|
|
183
245
|
<XIcon aria-hidden="true" className="size-3.5" />
|
|
184
246
|
{active === "reject" ? messages.rejecting : messages.reject}
|
|
@@ -187,7 +249,7 @@ export function ApprovalSurface({
|
|
|
187
249
|
type="button"
|
|
188
250
|
disabled={busy}
|
|
189
251
|
onClick={() => void decide(approval, "approve")}
|
|
190
|
-
className="inline-flex min-h-
|
|
252
|
+
className="inline-flex min-h-8 items-center gap-1.5 rounded-og-md border border-og-primary-border bg-og-primary px-3 py-1 text-og-sm font-medium text-og-primary-fg transition-colors hover:bg-og-primary-hover disabled:opacity-50"
|
|
191
253
|
>
|
|
192
254
|
<CheckIcon aria-hidden="true" className="size-3.5" />
|
|
193
255
|
{active === "approve" ? messages.approving : messages.approve}
|
|
@@ -199,10 +261,72 @@ export function ApprovalSurface({
|
|
|
199
261
|
</div>
|
|
200
262
|
|
|
201
263
|
{errorMessage ? (
|
|
202
|
-
<p role="alert" className="text-og-sm text-og-status-failed">
|
|
264
|
+
<p role="alert" className="px-4 pb-3 text-og-sm text-og-status-failed">
|
|
203
265
|
{errorMessage}
|
|
204
266
|
</p>
|
|
205
267
|
) : null}
|
|
206
268
|
</section>
|
|
207
269
|
);
|
|
208
270
|
}
|
|
271
|
+
|
|
272
|
+
function ApprovalDetails({
|
|
273
|
+
approval,
|
|
274
|
+
messages,
|
|
275
|
+
}: {
|
|
276
|
+
approval: PendingApproval;
|
|
277
|
+
messages: ApprovalSurfaceMessages;
|
|
278
|
+
}) {
|
|
279
|
+
const [showExact, setShowExact] = useState(false);
|
|
280
|
+
const fields = approvalArgumentFields(approval.arguments);
|
|
281
|
+
const exact = isEmptyArguments(approval.arguments)
|
|
282
|
+
? null
|
|
283
|
+
: approvalArgumentsPreview(approval.arguments);
|
|
284
|
+
return (
|
|
285
|
+
<div className="flex min-w-0 items-start gap-3">
|
|
286
|
+
<span className="mt-0.5 inline-flex size-8 shrink-0 items-center justify-center rounded-og-md bg-og-status-waiting/12 text-og-status-waiting">
|
|
287
|
+
<ShieldCheckIcon aria-hidden="true" className="size-4" />
|
|
288
|
+
</span>
|
|
289
|
+
<div className="min-w-0 flex-1">
|
|
290
|
+
<p className="text-og-xs font-medium text-og-status-waiting">{messages.title}</p>
|
|
291
|
+
<p className="mt-0.5 break-words text-og-md font-semibold text-og-fg">
|
|
292
|
+
{approval.display?.title ??
|
|
293
|
+
messages.formatToolName(approval.display?.toolName ?? approval.name)}
|
|
294
|
+
</p>
|
|
295
|
+
{approval.display?.accountLabel ? (
|
|
296
|
+
<p className="mt-0.5 break-words text-og-xs text-og-fg-muted">
|
|
297
|
+
{approval.display.accountLabel}
|
|
298
|
+
</p>
|
|
299
|
+
) : null}
|
|
300
|
+
{fields ? (
|
|
301
|
+
<dl className="m-0 mt-3 grid grid-cols-[minmax(5.5rem,max-content)_minmax(0,1fr)] gap-x-5 gap-y-1.5 text-og-sm">
|
|
302
|
+
{fields.map((field) => (
|
|
303
|
+
<Fragment key={field.key}>
|
|
304
|
+
<dt className="m-0 text-og-fg-muted">{field.label}</dt>
|
|
305
|
+
<dd className="m-0 max-h-32 min-w-0 overflow-auto whitespace-pre-wrap break-words text-og-fg">
|
|
306
|
+
{field.value}
|
|
307
|
+
</dd>
|
|
308
|
+
</Fragment>
|
|
309
|
+
))}
|
|
310
|
+
</dl>
|
|
311
|
+
) : null}
|
|
312
|
+
{exact !== null && (!fields || showExact) ? (
|
|
313
|
+
<pre className="mt-3 max-h-48 overflow-auto whitespace-pre-wrap break-words rounded-og-sm bg-og-fg/5 p-2.5 font-mono text-og-xs text-og-fg-muted">
|
|
314
|
+
{exact}
|
|
315
|
+
</pre>
|
|
316
|
+
) : null}
|
|
317
|
+
{fields && exact !== null ? (
|
|
318
|
+
<button
|
|
319
|
+
type="button"
|
|
320
|
+
aria-expanded={showExact}
|
|
321
|
+
onClick={() => setShowExact((value) => !value)}
|
|
322
|
+
className="mt-2 text-og-xs text-og-fg-muted underline-offset-2 hover:text-og-fg hover:underline"
|
|
323
|
+
>
|
|
324
|
+
{showExact
|
|
325
|
+
? (messages.hideDetails ?? defaultApprovalSurfaceMessages.hideDetails)
|
|
326
|
+
: (messages.showDetails ?? defaultApprovalSurfaceMessages.showDetails)}
|
|
327
|
+
</button>
|
|
328
|
+
) : null}
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
@@ -20,7 +20,6 @@ import type {
|
|
|
20
20
|
SiteAuthConnection,
|
|
21
21
|
} from "@opengeni/sdk/interaction";
|
|
22
22
|
import { interactionControlFailureFromError } from "@opengeni/sdk/interaction";
|
|
23
|
-
import { OpenGeniApiError } from "@opengeni/sdk";
|
|
24
23
|
import {
|
|
25
24
|
BugIcon,
|
|
26
25
|
ArchiveIcon,
|
|
@@ -71,7 +70,10 @@ import { useSiteAuthConnections } from "../hooks/use-site-auth-connections";
|
|
|
71
70
|
import { cn } from "../lib/cn";
|
|
72
71
|
import { copyTextToClipboard } from "../lib/clipboard";
|
|
73
72
|
import { formatBytes } from "../lib/format";
|
|
74
|
-
import {
|
|
73
|
+
import {
|
|
74
|
+
isInteractionControlUnavailable,
|
|
75
|
+
isSourcePlacementChangedError,
|
|
76
|
+
} from "../lib/interaction-errors";
|
|
75
77
|
import type { EmbeddedBrowserInteractionClientOverride } from "../session-context";
|
|
76
78
|
import { browserKey, HUMAN_BROWSER_HOME_URL, normalizeBrowserAddress } from "./browser-input";
|
|
77
79
|
import { InteractionInterventionBanner } from "./interaction-intervention-banner";
|
|
@@ -434,7 +436,7 @@ export function BrowserViewer({
|
|
|
434
436
|
}, [frameIsLive]);
|
|
435
437
|
const supportsLiveFrames =
|
|
436
438
|
(browser.session ?? selectedRegistrySession)?.capabilities.liveFrames === true;
|
|
437
|
-
const controlUnavailable =
|
|
439
|
+
const controlUnavailable = isInteractionControlUnavailable(browser.error);
|
|
438
440
|
const connectionError = controlUnavailable ? browser.error : (frames.error ?? browser.error);
|
|
439
441
|
// A managed controller can reject a stale attachment while the same browser
|
|
440
442
|
// remains healthy. Only extension-attached Chrome requires a new browser on
|
|
@@ -919,6 +921,32 @@ export function BrowserViewer({
|
|
|
919
921
|
.catch((cause) => notifyError(cause, "Could not reload."));
|
|
920
922
|
}}
|
|
921
923
|
/>
|
|
924
|
+
{browser.inputFailure ? (
|
|
925
|
+
<div
|
|
926
|
+
role="alert"
|
|
927
|
+
aria-label="Browser input status"
|
|
928
|
+
className="flex shrink-0 items-center gap-2 border-t border-og-border bg-og-surface-1 px-3 py-2 text-og-xs"
|
|
929
|
+
>
|
|
930
|
+
<CircleAlertIcon className="size-4 shrink-0 text-og-status-failed" />
|
|
931
|
+
<div className="min-w-0 flex-1">
|
|
932
|
+
<span className="font-medium">
|
|
933
|
+
{browser.inputFailure.state === "failed"
|
|
934
|
+
? "Browser input failed"
|
|
935
|
+
: "Input result unknown"}
|
|
936
|
+
</span>
|
|
937
|
+
<p className="text-og-fg-subtle">
|
|
938
|
+
{browser.inputFailure.error?.message ?? "Inspect the page before continuing."}
|
|
939
|
+
</p>
|
|
940
|
+
</div>
|
|
941
|
+
<button
|
|
942
|
+
type="button"
|
|
943
|
+
onClick={() => void browser.refresh()}
|
|
944
|
+
className="shrink-0 rounded border border-og-border px-2 py-1 transition hover:bg-og-surface-2"
|
|
945
|
+
>
|
|
946
|
+
Check browser
|
|
947
|
+
</button>
|
|
948
|
+
</div>
|
|
949
|
+
) : null}
|
|
922
950
|
<div className="relative flex min-h-0 min-w-0 flex-1 overflow-hidden">
|
|
923
951
|
<BrowserViewport
|
|
924
952
|
// A new control scope must discard every buffered gesture and queued action.
|
|
@@ -1022,6 +1050,7 @@ export function BrowserViewer({
|
|
|
1022
1050
|
profile={selectedProfile}
|
|
1023
1051
|
target={browser.selectedTarget}
|
|
1024
1052
|
observation={browser.observation}
|
|
1053
|
+
inputFailure={browser.inputFailure}
|
|
1025
1054
|
connectionState={displayConnectionState}
|
|
1026
1055
|
refreshing={registry.refreshing}
|
|
1027
1056
|
diagnosticsOpen={diagnosticsView !== null}
|
|
@@ -2640,6 +2669,7 @@ function BrowserStatusBar(props: {
|
|
|
2640
2669
|
profile: BrowserIdentity | null;
|
|
2641
2670
|
target: BrowserTarget | null;
|
|
2642
2671
|
observation: ReturnType<typeof useBrowserSession>["observation"];
|
|
2672
|
+
inputFailure: ReturnType<typeof useBrowserSession>["inputFailure"];
|
|
2643
2673
|
connectionState: string;
|
|
2644
2674
|
refreshing: boolean;
|
|
2645
2675
|
diagnosticsOpen: boolean;
|
|
@@ -2667,6 +2697,11 @@ function BrowserStatusBar(props: {
|
|
|
2667
2697
|
? "Semantic"
|
|
2668
2698
|
: browserConnectionLabel(props.connectionState)}
|
|
2669
2699
|
</span>
|
|
2700
|
+
{props.inputFailure ? (
|
|
2701
|
+
<span className="text-og-status-failed">
|
|
2702
|
+
{props.inputFailure.state === "failed" ? "Input failed" : "Input result unknown"}
|
|
2703
|
+
</span>
|
|
2704
|
+
) : null}
|
|
2670
2705
|
<span>{props.profile?.name ?? "Temporary browser"}</span>
|
|
2671
2706
|
<span className="min-w-0 flex-1 truncate">{props.target?.title}</span>
|
|
2672
2707
|
{props.refreshing ? <LoaderCircleIcon className="size-3 animate-spin" /> : null}
|
|
@@ -3280,12 +3315,6 @@ function attachedChromeGenerationLoss(
|
|
|
3280
3315
|
return lost?.placement.kind === "attached_device" ? { deviceId: lost.placement.deviceId } : null;
|
|
3281
3316
|
}
|
|
3282
3317
|
|
|
3283
|
-
function isBrowserControlUnavailable(error: Error | null): boolean {
|
|
3284
|
-
// Receiving pixels proves only the media channel. A failed control request
|
|
3285
|
-
// must not leave a frozen screenshot presented as an interactive live page.
|
|
3286
|
-
return error instanceof OpenGeniApiError && (error.status >= 500 || error.status === 0);
|
|
3287
|
-
}
|
|
3288
|
-
|
|
3289
3318
|
function isAttachedChromeGenerationLossError(error: Error | null): boolean {
|
|
3290
3319
|
if (!error) return false;
|
|
3291
3320
|
return /placement instance changed|controller authority changed/i.test(error.message);
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
type RefObject,
|
|
37
37
|
} from "react";
|
|
38
38
|
import { argHint, defaultCommands } from "../commands/registry";
|
|
39
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
39
40
|
import type { Notice, SlashCommand } from "../commands/types";
|
|
40
41
|
import type { ComposerState } from "../hooks/use-composer";
|
|
41
42
|
import { shouldSteerOnKey, shouldSubmitOnKey } from "../hooks/use-composer";
|
|
@@ -175,7 +176,9 @@ export const defaultChatComposerMessages: ChatComposerMessages = {
|
|
|
175
176
|
messagePlaceholder: "Message the agent…",
|
|
176
177
|
pausedPlaceholder: "Message the agent — it will wait in the queue…",
|
|
177
178
|
inputLabel: "Message the agent",
|
|
178
|
-
|
|
179
|
+
// Shortcuts live in the send button's title; the footer stays quiet unless
|
|
180
|
+
// the host supplies its own hint.
|
|
181
|
+
keyboardHint: "",
|
|
179
182
|
slashCommandBlocked:
|
|
180
183
|
"That's a slash command — press Enter in the command list to run it, or edit the line to send a message.",
|
|
181
184
|
controlChangedError:
|
|
@@ -374,6 +377,7 @@ export function useChatComposerController({
|
|
|
374
377
|
onPaste,
|
|
375
378
|
messages: messageOverrides,
|
|
376
379
|
}: UseChatComposerControllerOptions) {
|
|
380
|
+
const formatError = useErrorMessage();
|
|
377
381
|
const messages = useMemo(
|
|
378
382
|
() => ({ ...defaultChatComposerMessages, ...messageOverrides }),
|
|
379
383
|
[messageOverrides],
|
|
@@ -663,7 +667,10 @@ export function useChatComposerController({
|
|
|
663
667
|
tone: "error" as const,
|
|
664
668
|
message: /control changed|paused while/i.test(delivery.error.message)
|
|
665
669
|
? messages.controlChangedError
|
|
666
|
-
:
|
|
670
|
+
: formatError(
|
|
671
|
+
delivery.error,
|
|
672
|
+
composerSubmissionErrorMessage(delivery.error) || messages.sendFailedError,
|
|
673
|
+
),
|
|
667
674
|
}
|
|
668
675
|
: null);
|
|
669
676
|
useEffect(() => {
|