@opengeni/react 7.6.1-canary.37073905159001 → 7.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +2 -19
  2. package/dist/{browser-viewer-63TXMINB.js → browser-viewer-5X47I6T2.js} +5 -6
  3. package/dist/{chunk-TKB4SB44.js → chunk-45GE35O7.js} +2 -10
  4. package/dist/chunk-45GE35O7.js.map +1 -0
  5. package/dist/{chunk-DOJWJQ3F.js → chunk-5EXGU7UF.js} +3 -3
  6. package/dist/{chunk-YN7I3STW.js → chunk-6NEZPF53.js} +32 -153
  7. package/dist/chunk-6NEZPF53.js.map +1 -0
  8. package/dist/{chunk-I7IWRQLN.js → chunk-BI57CQUV.js} +2 -2
  9. package/dist/{chunk-6ZSWSH4I.js → chunk-CHPKSLPN.js} +1 -3
  10. package/dist/chunk-CHPKSLPN.js.map +1 -0
  11. package/dist/{chunk-Q5QBEZIQ.js → chunk-DV2DBNPQ.js} +29 -48
  12. package/dist/chunk-DV2DBNPQ.js.map +1 -0
  13. package/dist/{chunk-RDKBXMWX.js → chunk-FGBCYQOT.js} +74 -120
  14. package/dist/chunk-FGBCYQOT.js.map +1 -0
  15. package/dist/{chunk-AAG4R3QN.js → chunk-KGENSZKS.js} +3 -4
  16. package/dist/chunk-KGENSZKS.js.map +1 -0
  17. package/dist/{chunk-YQEAA6LT.js → chunk-KZ73RL76.js} +3 -7
  18. package/dist/chunk-KZ73RL76.js.map +1 -0
  19. package/dist/{chunk-LFXYGZWK.js → chunk-LZ3KHA55.js} +60 -169
  20. package/dist/chunk-LZ3KHA55.js.map +1 -0
  21. package/dist/{chunk-2QUSQVF2.js → chunk-UINDK6C3.js} +3 -3
  22. package/dist/{chunk-UTRTG6LH.js → chunk-UJRYEWZI.js} +8 -48
  23. package/dist/chunk-UJRYEWZI.js.map +1 -0
  24. package/dist/{chunk-XOAMRDDK.js → chunk-VGIKFIO2.js} +3 -6
  25. package/dist/chunk-VGIKFIO2.js.map +1 -0
  26. package/dist/{chunk-ELCE5SXV.js → chunk-WZ55DVGK.js} +4 -8
  27. package/dist/chunk-WZ55DVGK.js.map +1 -0
  28. package/dist/{chunk-IQUCRAT7.js → chunk-XA6R7DJQ.js} +3 -3
  29. package/dist/{chunk-TOAH2BKA.js → chunk-YCGBREUV.js} +2 -6
  30. package/dist/{chunk-TOAH2BKA.js.map → chunk-YCGBREUV.js.map} +1 -1
  31. package/dist/{chunk-HNFCAQ5R.js → chunk-YRYF2GLG.js} +31 -39
  32. package/dist/chunk-YRYF2GLG.js.map +1 -0
  33. package/dist/{chunk-GISEVGS6.js → chunk-YSMMDOOE.js} +2 -10
  34. package/dist/chunk-YSMMDOOE.js.map +1 -0
  35. package/dist/{chunk-CWWKTNKK.js → chunk-Z3DISEQN.js} +18 -35
  36. package/dist/chunk-Z3DISEQN.js.map +1 -0
  37. package/dist/client.d.ts +1 -4
  38. package/dist/commands/types.d.ts +0 -2
  39. package/dist/components/approval-surface.d.ts +0 -15
  40. package/dist/composer.js +7 -8
  41. package/dist/{computer-viewer-X2MBRZY4.js → computer-viewer-7GF35SNY.js} +4 -5
  42. package/dist/connect.js +3 -3
  43. package/dist/hooks/use-browser-session.d.ts +0 -4
  44. package/dist/hooks/use-composer.d.ts +0 -2
  45. package/dist/hooks/use-computer-session.d.ts +0 -2
  46. package/dist/hooks/use-file-attachments.d.ts +0 -2
  47. package/dist/hooks/use-session-events.d.ts +1 -1
  48. package/dist/hooks/use-workspace-sessions.d.ts +5 -15
  49. package/dist/index.d.ts +2 -2
  50. package/dist/index.js +21 -28
  51. package/dist/index.js.map +1 -1
  52. package/dist/interaction.d.ts +1 -1
  53. package/dist/interaction.js +5 -6
  54. package/dist/interaction.js.map +1 -1
  55. package/dist/{latest-question-CNEZ5KAN.js → latest-question-3BJGKQQ6.js} +3 -3
  56. package/dist/lib/format.d.ts +3 -27
  57. package/dist/lib/interaction-errors.d.ts +0 -3
  58. package/dist/machines.js +4 -5
  59. package/dist/{model-policy-picker-menu-JSA64TNN.js → model-policy-picker-menu-PUTA5JOB.js} +4 -4
  60. package/dist/model-policy.js +1 -1
  61. package/dist/provider.d.ts +1 -5
  62. package/dist/{queue-surface-implementation-HENWYFWA.js → queue-surface-implementation-2CAQAIHM.js} +2 -3
  63. package/dist/{queue-surface-implementation-HENWYFWA.js.map → queue-surface-implementation-2CAQAIHM.js.map} +1 -1
  64. package/dist/realtime.js +2 -2
  65. package/dist/{sandbox-files-BBC47SLD.js → sandbox-files-AEJTWGT4.js} +3 -4
  66. package/dist/session-ui.d.ts +1 -1
  67. package/dist/session-ui.js +12 -13
  68. package/dist/session-ui.js.map +1 -1
  69. package/dist/session.js +6 -7
  70. package/dist/usage.js +1 -2
  71. package/dist/usage.js.map +1 -1
  72. package/package.json +3 -3
  73. package/src/client.ts +1 -5
  74. package/src/commands/registry.ts +15 -18
  75. package/src/commands/types.ts +0 -2
  76. package/src/components/approval-surface.tsx +42 -166
  77. package/src/components/browser-viewer.tsx +9 -38
  78. package/src/components/composer.tsx +2 -9
  79. package/src/components/computer-viewer.tsx +30 -160
  80. package/src/components/generated-video-player.tsx +2 -4
  81. package/src/components/human-input-form.tsx +5 -18
  82. package/src/components/message-timeline.tsx +2 -4
  83. package/src/components/open-geni-chat.tsx +4 -11
  84. package/src/components/queue-surface-state.tsx +1 -3
  85. package/src/components/session-chrome.tsx +2 -5
  86. package/src/components/session-commands-panel.tsx +1 -3
  87. package/src/components/session-conversation.tsx +2 -4
  88. package/src/components/session-list.tsx +5 -7
  89. package/src/components/session-mcp-capability-card.tsx +1 -11
  90. package/src/hooks/internal.ts +2 -3
  91. package/src/hooks/use-browser-session.ts +79 -115
  92. package/src/hooks/use-composer.ts +31 -70
  93. package/src/hooks/use-computer-session.ts +6 -57
  94. package/src/hooks/use-file-attachments.ts +3 -16
  95. package/src/hooks/use-goal.ts +1 -2
  96. package/src/hooks/use-session-capabilities.ts +2 -3
  97. package/src/hooks/use-session-events.ts +8 -15
  98. package/src/hooks/use-slash-commands.ts +11 -6
  99. package/src/hooks/use-turn-queue.ts +1 -2
  100. package/src/hooks/use-workspace-sessions.ts +7 -40
  101. package/src/index.ts +2 -7
  102. package/src/interaction.ts +0 -1
  103. package/src/lib/format.ts +9 -73
  104. package/src/lib/interaction-errors.ts +0 -15
  105. package/src/model-policy.ts +0 -5
  106. package/src/provider.tsx +4 -12
  107. package/src/session-ui.ts +1 -1
  108. package/src/timeline/projection.ts +1 -12
  109. package/styles/compiled.css +0 -14
  110. package/dist/chunk-6ZSWSH4I.js.map +0 -1
  111. package/dist/chunk-AAG4R3QN.js.map +0 -1
  112. package/dist/chunk-CWWKTNKK.js.map +0 -1
  113. package/dist/chunk-ELCE5SXV.js.map +0 -1
  114. package/dist/chunk-GISEVGS6.js.map +0 -1
  115. package/dist/chunk-HNFCAQ5R.js.map +0 -1
  116. package/dist/chunk-LFXYGZWK.js.map +0 -1
  117. package/dist/chunk-Q5QBEZIQ.js.map +0 -1
  118. package/dist/chunk-RDKBXMWX.js.map +0 -1
  119. package/dist/chunk-TKB4SB44.js.map +0 -1
  120. package/dist/chunk-UGLEYEMP.js +0 -33
  121. package/dist/chunk-UGLEYEMP.js.map +0 -1
  122. package/dist/chunk-UTRTG6LH.js.map +0 -1
  123. package/dist/chunk-XOAMRDDK.js.map +0 -1
  124. package/dist/chunk-YN7I3STW.js.map +0 -1
  125. package/dist/chunk-YQEAA6LT.js.map +0 -1
  126. package/dist/lib/error-message.d.ts +0 -7
  127. package/src/lib/error-message.ts +0 -37
  128. /package/dist/{browser-viewer-63TXMINB.js.map → browser-viewer-5X47I6T2.js.map} +0 -0
  129. /package/dist/{chunk-DOJWJQ3F.js.map → chunk-5EXGU7UF.js.map} +0 -0
  130. /package/dist/{chunk-I7IWRQLN.js.map → chunk-BI57CQUV.js.map} +0 -0
  131. /package/dist/{chunk-2QUSQVF2.js.map → chunk-UINDK6C3.js.map} +0 -0
  132. /package/dist/{chunk-IQUCRAT7.js.map → chunk-XA6R7DJQ.js.map} +0 -0
  133. /package/dist/{computer-viewer-X2MBRZY4.js.map → computer-viewer-7GF35SNY.js.map} +0 -0
  134. /package/dist/{latest-question-CNEZ5KAN.js.map → latest-question-3BJGKQQ6.js.map} +0 -0
  135. /package/dist/{model-policy-picker-menu-JSA64TNN.js.map → model-policy-picker-menu-PUTA5JOB.js.map} +0 -0
  136. /package/dist/{sandbox-files-BBC47SLD.js.map → sandbox-files-AEJTWGT4.js.map} +0 -0
@@ -1,26 +1,21 @@
1
1
  import { CheckIcon, ShieldCheckIcon, XIcon } from "lucide-react";
2
- import { Fragment, useEffect, useId, useRef, useState, type ReactNode } from "react";
2
+ import { 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";
6
5
  import { toolDisplayName } from "../timeline/tool-display-name";
7
6
 
8
7
  export type ApprovalSurfaceMessages = {
9
8
  title: string;
10
- /** Screen-reader context for the decision; not shown visually. */
11
9
  description: string;
12
10
  approve: string;
13
11
  reject: string;
14
12
  approving: string;
15
13
  rejecting: string;
16
14
  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;
20
15
  };
21
16
 
22
17
  export const defaultApprovalSurfaceMessages: ApprovalSurfaceMessages = {
23
- title: "Approval needed",
18
+ title: "Approval required",
24
19
  description: "Review the requested action before the agent continues.",
25
20
  approve: "Approve",
26
21
  reject: "Reject",
@@ -30,8 +25,6 @@ export const defaultApprovalSurfaceMessages: ApprovalSurfaceMessages = {
30
25
  name.includes("__") || /^[a-f0-9]{64}$/.test(name)
31
26
  ? toolDisplayName(name)
32
27
  : name.replaceAll("_", " ").replaceAll(".", " › "),
33
- showDetails: "Show exact arguments",
34
- hideDetails: "Hide exact arguments",
35
28
  };
36
29
 
37
30
  export type ApprovalSurfaceProps = {
@@ -46,8 +39,6 @@ export type ApprovalSurfaceProps = {
46
39
  };
47
40
 
48
41
  const APPROVAL_ARGUMENT_PREVIEW_CHARACTERS = 4_000;
49
- const APPROVAL_FIELD_LIMIT = 12;
50
- const APPROVAL_LIST_LIMIT = 10;
51
42
 
52
43
  function approvalArgumentsPreview(value: unknown): string | null {
53
44
  if (value === undefined) return null;
@@ -63,68 +54,6 @@ function approvalArgumentsPreview(value: unknown): string | null {
63
54
  return `${characters.slice(0, APPROVAL_ARGUMENT_PREVIEW_CHARACTERS).join("")}\n… ${characters.length - APPROVAL_ARGUMENT_PREVIEW_CHARACTERS} characters omitted`;
64
55
  }
65
56
 
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
-
128
57
  function approvalOwnershipKey(approval: PendingApproval): string {
129
58
  return `${approval.id}\u0000${approval.name}\u0000${approvalArgumentsPreview(approval.arguments) ?? ""}`;
130
59
  }
@@ -145,8 +74,6 @@ export function ApprovalSurface({
145
74
  className,
146
75
  }: ApprovalSurfaceProps) {
147
76
  const titleId = useId();
148
- const formatError = useErrorMessage();
149
- const descriptionId = useId();
150
77
  const messages = { ...defaultApprovalSurfaceMessages, ...overrides };
151
78
  const [pending, setPending] = useState<{
152
79
  approvalKey: string;
@@ -154,7 +81,7 @@ export function ApprovalSurface({
154
81
  token: symbol;
155
82
  } | null>(null);
156
83
  const pendingRef = useRef<{ approvalKey: string; token: symbol } | null>(null);
157
- const [decisionError, setDecisionError] = useState<{ cause: unknown } | null>(null);
84
+ const [decisionError, setDecisionError] = useState<Error | null>(null);
158
85
 
159
86
  useEffect(() => {
160
87
  if (
@@ -189,58 +116,69 @@ export function ApprovalSurface({
189
116
  if (pendingRef.current?.token === token) {
190
117
  pendingRef.current = null;
191
118
  setPending((current) => (current?.token === token ? null : current));
192
- setDecisionError({ cause });
119
+ setDecisionError(cause instanceof Error ? cause : new Error(String(cause)));
193
120
  }
194
121
  }
195
122
  };
196
- const errorMessage = decisionError
197
- ? formatError(decisionError.cause)
198
- : error instanceof Error
199
- ? formatError(error)
200
- : error;
123
+ const errorMessage = decisionError?.message ?? (error instanceof Error ? error.message : error);
201
124
 
202
125
  return (
203
126
  <section
204
127
  className={cn(
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",
128
+ "og-root flex w-full flex-col gap-3 rounded-og-lg border border-og-status-waiting/35 bg-og-status-waiting/5 p-4 shadow-og-sm",
206
129
  className,
207
130
  )}
208
131
  aria-labelledby={titleId}
209
- aria-describedby={descriptionId}
210
- data-og-approval-surface=""
211
132
  >
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">
133
+ <header className="flex items-start gap-3">
134
+ <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">
135
+ <ShieldCheckIcon aria-hidden="true" className="size-4" />
136
+ </span>
137
+ <div className="min-w-0">
138
+ <h2 id={titleId} className="text-og-md font-semibold text-og-fg">
139
+ {messages.title}
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">
219
146
  {approvals.map((approval) => {
220
147
  const approvalKey = approvalOwnershipKey(approval);
221
148
  const active = pending?.approvalKey === approvalKey ? pending.decision : null;
149
+ const argumentsPreview = approvalArgumentsPreview(approval.arguments);
222
150
  return (
223
151
  <article
224
152
  key={approval.id}
225
153
  data-approval-id={approval.id}
226
- className="flex flex-col gap-3 p-4"
154
+ className="rounded-og-md border border-og-border bg-og-surface-1 p-3"
227
155
  >
228
156
  {renderApproval ? (
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}
232
- </p>
233
- {renderApproval(approval)}
234
- </div>
157
+ renderApproval(approval)
235
158
  ) : (
236
- <ApprovalDetails approval={approval} messages={messages} />
159
+ <>
160
+ <p className="text-og-sm font-medium text-og-fg">
161
+ {approval.display?.title ??
162
+ messages.formatToolName(approval.display?.toolName ?? approval.name)}
163
+ </p>
164
+ {approval.display?.accountLabel ? (
165
+ <p className="mt-1 break-words text-og-xs text-og-fg-muted">
166
+ {approval.display.accountLabel}
167
+ </p>
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
+ </>
237
175
  )}
238
- <div className="flex flex-wrap items-center justify-end gap-2">
176
+ <div className="mt-3 flex flex-wrap justify-end gap-2">
239
177
  <button
240
178
  type="button"
241
179
  disabled={busy}
242
180
  onClick={() => void decide(approval, "reject")}
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"
181
+ className="inline-flex min-h-9 items-center gap-1.5 rounded-og-md border border-og-border px-3 py-1.5 text-og-sm font-medium text-og-fg-muted transition-colors hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-50"
244
182
  >
245
183
  <XIcon aria-hidden="true" className="size-3.5" />
246
184
  {active === "reject" ? messages.rejecting : messages.reject}
@@ -249,7 +187,7 @@ export function ApprovalSurface({
249
187
  type="button"
250
188
  disabled={busy}
251
189
  onClick={() => void decide(approval, "approve")}
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"
190
+ className="inline-flex min-h-9 items-center gap-1.5 rounded-og-md border border-og-primary-border bg-og-primary text-og-primary-fg px-3 py-1.5 text-og-sm font-medium transition-colors hover:bg-og-primary-hover disabled:opacity-50"
253
191
  >
254
192
  <CheckIcon aria-hidden="true" className="size-3.5" />
255
193
  {active === "approve" ? messages.approving : messages.approve}
@@ -261,72 +199,10 @@ export function ApprovalSurface({
261
199
  </div>
262
200
 
263
201
  {errorMessage ? (
264
- <p role="alert" className="px-4 pb-3 text-og-sm text-og-status-failed">
202
+ <p role="alert" className="text-og-sm text-og-status-failed">
265
203
  {errorMessage}
266
204
  </p>
267
205
  ) : null}
268
206
  </section>
269
207
  );
270
208
  }
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,6 +20,7 @@ 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";
23
24
  import {
24
25
  BugIcon,
25
26
  ArchiveIcon,
@@ -70,10 +71,7 @@ import { useSiteAuthConnections } from "../hooks/use-site-auth-connections";
70
71
  import { cn } from "../lib/cn";
71
72
  import { copyTextToClipboard } from "../lib/clipboard";
72
73
  import { formatBytes } from "../lib/format";
73
- import {
74
- isInteractionControlUnavailable,
75
- isSourcePlacementChangedError,
76
- } from "../lib/interaction-errors";
74
+ import { isSourcePlacementChangedError } from "../lib/interaction-errors";
77
75
  import type { EmbeddedBrowserInteractionClientOverride } from "../session-context";
78
76
  import { browserKey, HUMAN_BROWSER_HOME_URL, normalizeBrowserAddress } from "./browser-input";
79
77
  import { InteractionInterventionBanner } from "./interaction-intervention-banner";
@@ -436,7 +434,7 @@ export function BrowserViewer({
436
434
  }, [frameIsLive]);
437
435
  const supportsLiveFrames =
438
436
  (browser.session ?? selectedRegistrySession)?.capabilities.liveFrames === true;
439
- const controlUnavailable = isInteractionControlUnavailable(browser.error);
437
+ const controlUnavailable = isBrowserControlUnavailable(browser.error);
440
438
  const connectionError = controlUnavailable ? browser.error : (frames.error ?? browser.error);
441
439
  // A managed controller can reject a stale attachment while the same browser
442
440
  // remains healthy. Only extension-attached Chrome requires a new browser on
@@ -921,32 +919,6 @@ export function BrowserViewer({
921
919
  .catch((cause) => notifyError(cause, "Could not reload."));
922
920
  }}
923
921
  />
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}
950
922
  <div className="relative flex min-h-0 min-w-0 flex-1 overflow-hidden">
951
923
  <BrowserViewport
952
924
  // A new control scope must discard every buffered gesture and queued action.
@@ -1050,7 +1022,6 @@ export function BrowserViewer({
1050
1022
  profile={selectedProfile}
1051
1023
  target={browser.selectedTarget}
1052
1024
  observation={browser.observation}
1053
- inputFailure={browser.inputFailure}
1054
1025
  connectionState={displayConnectionState}
1055
1026
  refreshing={registry.refreshing}
1056
1027
  diagnosticsOpen={diagnosticsView !== null}
@@ -2669,7 +2640,6 @@ function BrowserStatusBar(props: {
2669
2640
  profile: BrowserIdentity | null;
2670
2641
  target: BrowserTarget | null;
2671
2642
  observation: ReturnType<typeof useBrowserSession>["observation"];
2672
- inputFailure: ReturnType<typeof useBrowserSession>["inputFailure"];
2673
2643
  connectionState: string;
2674
2644
  refreshing: boolean;
2675
2645
  diagnosticsOpen: boolean;
@@ -2697,11 +2667,6 @@ function BrowserStatusBar(props: {
2697
2667
  ? "Semantic"
2698
2668
  : browserConnectionLabel(props.connectionState)}
2699
2669
  </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}
2705
2670
  <span>{props.profile?.name ?? "Temporary browser"}</span>
2706
2671
  <span className="min-w-0 flex-1 truncate">{props.target?.title}</span>
2707
2672
  {props.refreshing ? <LoaderCircleIcon className="size-3 animate-spin" /> : null}
@@ -3315,6 +3280,12 @@ function attachedChromeGenerationLoss(
3315
3280
  return lost?.placement.kind === "attached_device" ? { deviceId: lost.placement.deviceId } : null;
3316
3281
  }
3317
3282
 
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
+
3318
3289
  function isAttachedChromeGenerationLossError(error: Error | null): boolean {
3319
3290
  if (!error) return false;
3320
3291
  return /placement instance changed|controller authority changed/i.test(error.message);
@@ -36,7 +36,6 @@ import {
36
36
  type RefObject,
37
37
  } from "react";
38
38
  import { argHint, defaultCommands } from "../commands/registry";
39
- import { useErrorMessage } from "../lib/error-message";
40
39
  import type { Notice, SlashCommand } from "../commands/types";
41
40
  import type { ComposerState } from "../hooks/use-composer";
42
41
  import { shouldSteerOnKey, shouldSubmitOnKey } from "../hooks/use-composer";
@@ -176,9 +175,7 @@ export const defaultChatComposerMessages: ChatComposerMessages = {
176
175
  messagePlaceholder: "Message the agent…",
177
176
  pausedPlaceholder: "Message the agent — it will wait in the queue…",
178
177
  inputLabel: "Message the agent",
179
- // Shortcuts live in the send button's title; the footer stays quiet unless
180
- // the host supplies its own hint.
181
- keyboardHint: "",
178
+ keyboardHint: "Enter to queue · Cmd/Ctrl+Enter to steer · Shift+Enter for a new line",
182
179
  slashCommandBlocked:
183
180
  "That's a slash command — press Enter in the command list to run it, or edit the line to send a message.",
184
181
  controlChangedError:
@@ -377,7 +374,6 @@ export function useChatComposerController({
377
374
  onPaste,
378
375
  messages: messageOverrides,
379
376
  }: UseChatComposerControllerOptions) {
380
- const formatError = useErrorMessage();
381
377
  const messages = useMemo(
382
378
  () => ({ ...defaultChatComposerMessages, ...messageOverrides }),
383
379
  [messageOverrides],
@@ -667,10 +663,7 @@ export function useChatComposerController({
667
663
  tone: "error" as const,
668
664
  message: /control changed|paused while/i.test(delivery.error.message)
669
665
  ? messages.controlChangedError
670
- : formatError(
671
- delivery.error,
672
- composerSubmissionErrorMessage(delivery.error) || messages.sendFailedError,
673
- ),
666
+ : composerSubmissionErrorMessage(delivery.error) || messages.sendFailedError,
674
667
  }
675
668
  : null);
676
669
  useEffect(() => {