@opengeni/react 7.1.2 → 7.2.0-canary.36237839247001
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/dist/{browser-viewer-TZ6T76E3.js → browser-viewer-K2H3SLIW.js} +2 -2
- package/dist/{chunk-CP6IFD2Z.js → chunk-ETA37UZ7.js} +4 -7
- package/dist/chunk-ETA37UZ7.js.map +1 -0
- package/dist/{chunk-VHEY4CB3.js → chunk-RSFCYKA7.js} +5 -3
- package/dist/{chunk-VHEY4CB3.js.map → chunk-RSFCYKA7.js.map} +1 -1
- package/dist/{chunk-VI7HELBI.js → chunk-SJ5HE4ZA.js} +21 -2
- package/dist/chunk-SJ5HE4ZA.js.map +1 -0
- package/dist/{chunk-4ACTWOOW.js → chunk-VDSZ3AVO.js} +41 -18
- package/dist/chunk-VDSZ3AVO.js.map +1 -0
- package/dist/composer.js +1 -1
- package/dist/index.js +4 -4
- package/dist/interaction.js +1 -1
- package/dist/session-ui.js +3 -3
- package/dist/session.js +1 -1
- package/dist/timeline/types.d.ts +2 -0
- package/package.json +5 -5
- package/src/components/browser-viewer.tsx +1 -5
- package/src/components/composer-transcription-control.tsx +4 -2
- package/src/components/composer.tsx +45 -20
- package/src/hooks/use-browser-frame-stream.ts +3 -1
- package/src/hooks/use-voice-input.ts +12 -1
- package/src/timeline/compaction-copy.ts +2 -0
- package/src/timeline/projection.ts +31 -5
- package/src/timeline/types.ts +2 -0
- package/dist/chunk-4ACTWOOW.js.map +0 -1
- package/dist/chunk-CP6IFD2Z.js.map +0 -1
- package/dist/chunk-VI7HELBI.js.map +0 -1
- /package/dist/{browser-viewer-TZ6T76E3.js.map → browser-viewer-K2H3SLIW.js.map} +0 -0
package/dist/composer.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -114,7 +114,7 @@ import {
|
|
|
114
114
|
useWorkspaceModelCatalog,
|
|
115
115
|
userMessageLikelyNeedsDisclosure,
|
|
116
116
|
v4aToGitFileDiff
|
|
117
|
-
} from "./chunk-
|
|
117
|
+
} from "./chunk-RSFCYKA7.js";
|
|
118
118
|
import "./chunk-GEN54DU5.js";
|
|
119
119
|
import "./chunk-GBPCZH3Y.js";
|
|
120
120
|
import "./chunk-ZYRGEKWS.js";
|
|
@@ -142,7 +142,7 @@ import {
|
|
|
142
142
|
useSession,
|
|
143
143
|
useSessionEvents,
|
|
144
144
|
useTurnQueue
|
|
145
|
-
} from "./chunk-
|
|
145
|
+
} from "./chunk-SJ5HE4ZA.js";
|
|
146
146
|
import {
|
|
147
147
|
OpenGeniProvider
|
|
148
148
|
} from "./chunk-PAJU5KKL.js";
|
|
@@ -178,7 +178,7 @@ import {
|
|
|
178
178
|
useSlashCommands,
|
|
179
179
|
useTranscription,
|
|
180
180
|
useVoiceInput
|
|
181
|
-
} from "./chunk-
|
|
181
|
+
} from "./chunk-VDSZ3AVO.js";
|
|
182
182
|
import {
|
|
183
183
|
BillingClassMark,
|
|
184
184
|
ModelPolicyPicker,
|
|
@@ -8174,7 +8174,7 @@ import {
|
|
|
8174
8174
|
} from "lucide-react";
|
|
8175
8175
|
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
8176
8176
|
var LazyBrowserViewer = lazy2(async () => {
|
|
8177
|
-
const { BrowserViewer } = await import("./browser-viewer-
|
|
8177
|
+
const { BrowserViewer } = await import("./browser-viewer-K2H3SLIW.js");
|
|
8178
8178
|
return { default: BrowserViewer };
|
|
8179
8179
|
});
|
|
8180
8180
|
var LazyComputerViewer = lazy2(async () => {
|
package/dist/interaction.js
CHANGED
package/dist/session-ui.js
CHANGED
|
@@ -19,16 +19,16 @@ import {
|
|
|
19
19
|
setStartupDetails,
|
|
20
20
|
useStartupDetails,
|
|
21
21
|
userMessageLikelyNeedsDisclosure
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-RSFCYKA7.js";
|
|
23
23
|
import "./chunk-GEN54DU5.js";
|
|
24
24
|
import "./chunk-GBPCZH3Y.js";
|
|
25
25
|
import "./chunk-ZYRGEKWS.js";
|
|
26
26
|
import "./chunk-WYWRYUNQ.js";
|
|
27
27
|
import {
|
|
28
28
|
createOlderHistoryLoadReceipt
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-SJ5HE4ZA.js";
|
|
30
30
|
import "./chunk-PAJU5KKL.js";
|
|
31
|
-
import "./chunk-
|
|
31
|
+
import "./chunk-VDSZ3AVO.js";
|
|
32
32
|
import "./chunk-3LL47F4U.js";
|
|
33
33
|
import "./chunk-AKOMC3VO.js";
|
|
34
34
|
import "./chunk-IOLPHOF4.js";
|
package/dist/session.js
CHANGED
package/dist/timeline/types.d.ts
CHANGED
|
@@ -375,6 +375,8 @@ export type AuthNeededItem = {
|
|
|
375
375
|
authorizationUrl: string | null;
|
|
376
376
|
/** Agent-selected catalog recommendation. The host still owns authorization. */
|
|
377
377
|
capability?: NonNullable<ToolAuthNeededPayload["capability"]> | null | undefined;
|
|
378
|
+
/** Human-reviewed custom integration proposal. Never treated as catalog authority. */
|
|
379
|
+
setupRequest?: ToolAuthNeededPayload["setupRequest"] | null | undefined;
|
|
378
380
|
occurredAt: string;
|
|
379
381
|
};
|
|
380
382
|
export type TurnOutcome = "complete" | "failed" | "cancelled";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/react",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0-canary.36237839247001",
|
|
4
4
|
"description": "React hooks and styled components for OpenGeni: live session streaming, chat composer, message timeline, session status, and fleet views — token-themed (CSS variables), dark-first, built on Tailwind v4 + Radix + Motion.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"import": "./dist/session-ui.js"
|
|
52
52
|
},
|
|
53
53
|
"./accounts": {
|
|
54
|
-
"types": "./dist/accounts.
|
|
55
|
-
"import": "./dist/accounts.
|
|
54
|
+
"types": "./dist/accounts.d.ts",
|
|
55
|
+
"import": "./dist/accounts.js"
|
|
56
56
|
},
|
|
57
57
|
"./machines": {
|
|
58
58
|
"types": "./dist/machines.d.ts",
|
|
@@ -133,8 +133,8 @@
|
|
|
133
133
|
"@dnd-kit/utilities": "3.2.2",
|
|
134
134
|
"@fontsource-variable/noto-sans-arabic": "5.2.10",
|
|
135
135
|
"@fontsource-variable/noto-sans-jp": "5.2.10",
|
|
136
|
-
"@opengeni/connect": "^0.3.0",
|
|
137
|
-
"@opengeni/sdk": "^7.
|
|
136
|
+
"@opengeni/connect": "^0.3.0-canary.36237839247001",
|
|
137
|
+
"@opengeni/sdk": "^7.2.0-canary.36237839247001",
|
|
138
138
|
"clsx": "^2.1.1",
|
|
139
139
|
"dequal": "2.0.3",
|
|
140
140
|
"lucide-react": "^1.8.0",
|
|
@@ -342,12 +342,8 @@ export function BrowserViewer({
|
|
|
342
342
|
setResumeAttempt((visible) =>
|
|
343
343
|
visible?.sessionId === selectedRegistrySession.id ? null : visible,
|
|
344
344
|
);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
if (selectedRegistrySession.lifecycle === "suspended") {
|
|
348
|
-
void wakeBrowser(selectedRegistrySession);
|
|
349
345
|
}
|
|
350
|
-
}, [selectedRegistrySession
|
|
346
|
+
}, [selectedRegistrySession]);
|
|
351
347
|
|
|
352
348
|
const browser = useBrowserSession({
|
|
353
349
|
...override,
|
|
@@ -248,6 +248,7 @@ export function ComposerTranscriptionControl({
|
|
|
248
248
|
>
|
|
249
249
|
<button
|
|
250
250
|
type="button"
|
|
251
|
+
disabled={composer.disabled && !retrying}
|
|
251
252
|
onClick={() =>
|
|
252
253
|
savedTranscript
|
|
253
254
|
? void transcription.insertSavedTranscript()
|
|
@@ -265,7 +266,7 @@ export function ComposerTranscriptionControl({
|
|
|
265
266
|
className={cn(
|
|
266
267
|
"inline-flex size-7 shrink-0 items-center justify-center rounded-og-sm",
|
|
267
268
|
"bg-og-fg text-og-bg transition-colors duration-150 motion-reduce:transition-none",
|
|
268
|
-
"hover:bg-og-fg-muted pointer-coarse:size-11",
|
|
269
|
+
"hover:bg-og-fg-muted disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-11",
|
|
269
270
|
)}
|
|
270
271
|
>
|
|
271
272
|
{savedTranscript ? (
|
|
@@ -280,12 +281,13 @@ export function ComposerTranscriptionControl({
|
|
|
280
281
|
<Tip tip={messages.discardRecovered}>
|
|
281
282
|
<button
|
|
282
283
|
type="button"
|
|
284
|
+
disabled={composer.disabled}
|
|
283
285
|
onClick={() => void transcription.discard()}
|
|
284
286
|
aria-label={messages.discardRecovered}
|
|
285
287
|
className={cn(
|
|
286
288
|
"inline-flex size-7 shrink-0 items-center justify-center rounded-og-sm",
|
|
287
289
|
"text-og-fg-muted transition-colors duration-150 motion-reduce:transition-none",
|
|
288
|
-
"hover:bg-og-surface-3 hover:text-og-status-failed pointer-coarse:size-11",
|
|
290
|
+
"hover:bg-og-surface-3 hover:text-og-status-failed disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-11",
|
|
289
291
|
)}
|
|
290
292
|
>
|
|
291
293
|
<Trash2Icon className="size-3.5" />
|
|
@@ -457,9 +457,9 @@ export function useChatComposerController({
|
|
|
457
457
|
(event: DragEvent<HTMLDivElement>) => {
|
|
458
458
|
if (!attachments || !dragCarriesFiles(event)) return;
|
|
459
459
|
event.preventDefault();
|
|
460
|
-
setDragging(true);
|
|
460
|
+
if (!disabled) setDragging(true);
|
|
461
461
|
},
|
|
462
|
-
[attachments],
|
|
462
|
+
[attachments, disabled],
|
|
463
463
|
);
|
|
464
464
|
const handleDragLeave = useCallback(
|
|
465
465
|
(event: DragEvent<HTMLDivElement>) => {
|
|
@@ -474,11 +474,16 @@ export function useChatComposerController({
|
|
|
474
474
|
if (!attachments || !dragCarriesFiles(event)) return;
|
|
475
475
|
event.preventDefault();
|
|
476
476
|
setDragging(false);
|
|
477
|
-
if (event.dataTransfer.files.length > 0)
|
|
477
|
+
if (!disabled && event.dataTransfer.files.length > 0)
|
|
478
|
+
attachments.addFiles(event.dataTransfer.files);
|
|
478
479
|
},
|
|
479
|
-
[attachments],
|
|
480
|
+
[attachments, disabled],
|
|
480
481
|
);
|
|
481
482
|
|
|
483
|
+
useEffect(() => {
|
|
484
|
+
if (disabled) setDragging(false);
|
|
485
|
+
}, [disabled]);
|
|
486
|
+
|
|
482
487
|
const [notice, setNotice] = useState<Notice | null>(null);
|
|
483
488
|
const [helpOpen, setHelpOpen] = useState(false);
|
|
484
489
|
const [confirmState, setConfirmState] = useState<ConfirmState>(null);
|
|
@@ -624,17 +629,18 @@ export function useChatComposerController({
|
|
|
624
629
|
);
|
|
625
630
|
const handlePaste = useCallback(
|
|
626
631
|
(event: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
632
|
+
if (disabled) return;
|
|
627
633
|
onPaste?.(event);
|
|
628
634
|
attachments?.addFromPaste(event);
|
|
629
635
|
},
|
|
630
|
-
[attachments, onPaste],
|
|
636
|
+
[attachments, disabled, onPaste],
|
|
631
637
|
);
|
|
632
638
|
const handleFileChange = useCallback(
|
|
633
639
|
(event: ChangeEvent<HTMLInputElement>) => {
|
|
634
|
-
if (event.target.files) attachments?.addFiles(event.target.files);
|
|
640
|
+
if (!disabled && event.target.files) attachments?.addFiles(event.target.files);
|
|
635
641
|
event.target.value = "";
|
|
636
642
|
},
|
|
637
|
-
[attachments],
|
|
643
|
+
[attachments, disabled],
|
|
638
644
|
);
|
|
639
645
|
|
|
640
646
|
const helpCommands = useMemo(
|
|
@@ -975,6 +981,7 @@ export function Attachments() {
|
|
|
975
981
|
<AttachmentChips
|
|
976
982
|
attachments={controller.attachments.attachments}
|
|
977
983
|
messages={controller.messages}
|
|
984
|
+
disabled={controller.disabled}
|
|
978
985
|
onRemove={controller.attachments.remove}
|
|
979
986
|
onRetry={controller.attachments.retry}
|
|
980
987
|
onRetainPreview={controller.attachments.retainPreview}
|
|
@@ -996,6 +1003,26 @@ type OwnedInputProps =
|
|
|
996
1003
|
| "aria-keyshortcuts";
|
|
997
1004
|
export type ComposerInputProps = Omit<ComponentPropsWithoutRef<"textarea">, OwnedInputProps>;
|
|
998
1005
|
|
|
1006
|
+
const EDITING_FOCUS_SELECTOR =
|
|
1007
|
+
'input, textarea, select, [contenteditable="true"], [role="textbox"]';
|
|
1008
|
+
const POPUP_FOCUS_SELECTOR =
|
|
1009
|
+
'[role="menu"], [role="menubar"], [role="listbox"], [role="dialog"], [role="alertdialog"]';
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Initial focus is a convenience, not authority to move focus the person has
|
|
1013
|
+
* placed elsewhere. It yields to a field they started editing and to an open
|
|
1014
|
+
* menu, listbox, or dialog that does not contain the composer: moving focus
|
|
1015
|
+
* behind a non-modal popup dismisses it, and a modal surface owns focus until
|
|
1016
|
+
* it closes.
|
|
1017
|
+
*/
|
|
1018
|
+
function focusBelongsElsewhere(textarea: HTMLTextAreaElement): boolean {
|
|
1019
|
+
const active = textarea.ownerDocument.activeElement;
|
|
1020
|
+
if (!active || active === textarea) return false;
|
|
1021
|
+
if (active.closest(EDITING_FOCUS_SELECTOR)) return true;
|
|
1022
|
+
const popup = active.closest(POPUP_FOCUS_SELECTOR);
|
|
1023
|
+
return popup !== null && !popup.contains(textarea);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
999
1026
|
export const Input = forwardRef<HTMLTextAreaElement, ComposerInputProps>(function ComposerInput(
|
|
1000
1027
|
{ rows = 1, placeholder, className, "aria-label": ariaLabel, autoFocus = false, ...props },
|
|
1001
1028
|
forwardedRef,
|
|
@@ -1005,22 +1032,17 @@ export const Input = forwardRef<HTMLTextAreaElement, ComposerInputProps>(functio
|
|
|
1005
1032
|
const paletteOpen =
|
|
1006
1033
|
controller.paletteEnabled && controller.paletteMounted && controller.palette.open;
|
|
1007
1034
|
|
|
1008
|
-
//
|
|
1009
|
-
// hydrate)
|
|
1035
|
+
// Focus once the controller is interactive. The textarea can mount disabled
|
|
1036
|
+
// (create-session draft hydrate), and the route can finish loading after the
|
|
1037
|
+
// person has already moved on, so this deliberately does not use the native
|
|
1038
|
+
// autoFocus attribute: that would take focus at mount without the guard.
|
|
1010
1039
|
useEffect(() => {
|
|
1011
1040
|
if (!autoFocus || controller.disabled || autoFocusedRef.current) return;
|
|
1012
1041
|
const frame = window.requestAnimationFrame(() => {
|
|
1013
1042
|
const textarea = controller.textareaRef.current;
|
|
1014
1043
|
if (!textarea || textarea.disabled) return;
|
|
1015
1044
|
autoFocusedRef.current = true;
|
|
1016
|
-
|
|
1017
|
-
// field. Initial focus is a convenience, not authority to take their caret.
|
|
1018
|
-
const active = textarea.ownerDocument.activeElement;
|
|
1019
|
-
if (
|
|
1020
|
-
active !== textarea &&
|
|
1021
|
-
active?.closest('input, textarea, select, [contenteditable="true"], [role="textbox"]')
|
|
1022
|
-
)
|
|
1023
|
-
return;
|
|
1045
|
+
if (focusBelongsElsewhere(textarea)) return;
|
|
1024
1046
|
textarea.focus();
|
|
1025
1047
|
});
|
|
1026
1048
|
return () => window.cancelAnimationFrame(frame);
|
|
@@ -1041,7 +1063,6 @@ export const Input = forwardRef<HTMLTextAreaElement, ComposerInputProps>(functio
|
|
|
1041
1063
|
: (placeholder ?? controller.messages.messagePlaceholder)
|
|
1042
1064
|
}
|
|
1043
1065
|
disabled={controller.disabled}
|
|
1044
|
-
autoFocus={autoFocus && !controller.disabled}
|
|
1045
1066
|
aria-label={ariaLabel ?? controller.messages.inputLabel}
|
|
1046
1067
|
aria-keyshortcuts="Enter Meta+Enter Control+Enter Shift+Enter"
|
|
1047
1068
|
aria-autocomplete={
|
|
@@ -1679,6 +1700,7 @@ function ConfirmBar({
|
|
|
1679
1700
|
function AttachmentChips({
|
|
1680
1701
|
attachments,
|
|
1681
1702
|
messages,
|
|
1703
|
+
disabled,
|
|
1682
1704
|
onRemove,
|
|
1683
1705
|
onRetry,
|
|
1684
1706
|
onRetainPreview,
|
|
@@ -1686,6 +1708,7 @@ function AttachmentChips({
|
|
|
1686
1708
|
}: {
|
|
1687
1709
|
attachments: UseFileAttachmentsResult["attachments"];
|
|
1688
1710
|
messages: ChatComposerMessages;
|
|
1711
|
+
disabled: boolean;
|
|
1689
1712
|
onRemove: (id: string) => void;
|
|
1690
1713
|
onRetry?: ((id: string) => void) | undefined;
|
|
1691
1714
|
onRetainPreview: UseFileAttachmentsResult["retainPreview"];
|
|
@@ -1837,7 +1860,8 @@ function AttachmentChips({
|
|
|
1837
1860
|
<button
|
|
1838
1861
|
type="button"
|
|
1839
1862
|
onClick={() => onRetry(attachment.id)}
|
|
1840
|
-
|
|
1863
|
+
disabled={disabled}
|
|
1864
|
+
className="shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-10"
|
|
1841
1865
|
aria-label={messages.retryAttachment(attachment.name)}
|
|
1842
1866
|
>
|
|
1843
1867
|
<RotateCwIcon className="size-3.5" />
|
|
@@ -1847,7 +1871,8 @@ function AttachmentChips({
|
|
|
1847
1871
|
<button
|
|
1848
1872
|
type="button"
|
|
1849
1873
|
onClick={() => onRemove(attachment.id)}
|
|
1850
|
-
|
|
1874
|
+
disabled={disabled}
|
|
1875
|
+
className="shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-10"
|
|
1851
1876
|
aria-label={messages.removeAttachment(attachment.name)}
|
|
1852
1877
|
>
|
|
1853
1878
|
<XIcon className="size-3.5" />
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
STREAM_KIND_BROWSER,
|
|
20
20
|
STREAM_ROLE_CLIENT,
|
|
21
21
|
} from "../lib/relay-wire";
|
|
22
|
+
import { usePageLiveActivity } from "./internal";
|
|
22
23
|
|
|
23
24
|
export type BrowserFrameConnectionState =
|
|
24
25
|
| "idle"
|
|
@@ -73,7 +74,8 @@ export function useBrowserFrameStream(
|
|
|
73
74
|
options: UseBrowserFrameStreamOptions,
|
|
74
75
|
): UseBrowserFrameStreamResult {
|
|
75
76
|
const { client, workspaceId } = useEmbeddedBrowserInteraction(options);
|
|
76
|
-
const
|
|
77
|
+
const pageLive = usePageLiveActivity();
|
|
78
|
+
const enabled = (options.enabled ?? true) && pageLive;
|
|
77
79
|
const [nonce, setNonce] = useState(0);
|
|
78
80
|
const [result, setResult] = useState<Omit<UseBrowserFrameStreamResult, "reconnect">>({
|
|
79
81
|
state: "idle",
|
|
@@ -163,6 +163,7 @@ export function useVoiceInput({
|
|
|
163
163
|
const automaticRetryAttemptRef = useRef(0);
|
|
164
164
|
const ownerHeartbeatTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
165
165
|
const controllerRef = useRef<AbortController | null>(null);
|
|
166
|
+
const disabledRef = useRef(disabled);
|
|
166
167
|
const valueRef = useRef(value);
|
|
167
168
|
const storeRef = useRef<VoiceRecordingStore | null>(null);
|
|
168
169
|
const storePromiseRef = useRef<Promise<VoiceRecordingStore> | null>(null);
|
|
@@ -176,6 +177,7 @@ export function useVoiceInput({
|
|
|
176
177
|
const statusRef = useRef(status);
|
|
177
178
|
valueRef.current = value;
|
|
178
179
|
statusRef.current = status;
|
|
180
|
+
disabledRef.current = disabled;
|
|
179
181
|
workspaceIdRef.current = workspaceId;
|
|
180
182
|
|
|
181
183
|
const ensureOwnerId = useCallback(async (): Promise<string> => {
|
|
@@ -468,6 +470,14 @@ export function useVoiceInput({
|
|
|
468
470
|
return;
|
|
469
471
|
}
|
|
470
472
|
|
|
473
|
+
// Session creation can disable the composer while a recording is
|
|
474
|
+
// transcribing. Keep the durable transcript for explicit insertion
|
|
475
|
+
// later; never append text after Send captured its visible snapshot.
|
|
476
|
+
if (disabledRef.current) {
|
|
477
|
+
setStatus("transcript-ready");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
471
481
|
const next = appendFinalTranscript(valueRef.current, response.text);
|
|
472
482
|
if (next !== valueRef.current) {
|
|
473
483
|
valueRef.current = next;
|
|
@@ -944,6 +954,7 @@ export function useVoiceInput({
|
|
|
944
954
|
const insertSavedTranscript = useCallback(async (): Promise<void> => {
|
|
945
955
|
const manifest = manifestRef.current;
|
|
946
956
|
if (
|
|
957
|
+
disabledRef.current ||
|
|
947
958
|
!manifest ||
|
|
948
959
|
manifest.workspaceId !== workspaceId ||
|
|
949
960
|
manifest.finalizationState !== "transcript-ready" ||
|
|
@@ -955,7 +966,7 @@ export function useVoiceInput({
|
|
|
955
966
|
controllerRef.current?.abort();
|
|
956
967
|
controllerRef.current = null;
|
|
957
968
|
const store = await ensureStore();
|
|
958
|
-
if (generation !== generationRef.current) return;
|
|
969
|
+
if (generation !== generationRef.current || disabledRef.current) return;
|
|
959
970
|
const next = appendFinalTranscript(valueRef.current, manifest.transcriptText);
|
|
960
971
|
if (next !== valueRef.current) {
|
|
961
972
|
valueRef.current = next;
|
|
@@ -15,6 +15,8 @@ export function compactionSkipSubtitle(
|
|
|
15
15
|
return "No active history to compact";
|
|
16
16
|
case "replacement_not_smaller":
|
|
17
17
|
return "Checkpoint would not reduce memory size";
|
|
18
|
+
case "replacement_exceeds_model_budget":
|
|
19
|
+
return "Checkpoint exceeds this model's context budget. Chat history is unchanged.";
|
|
18
20
|
case "replacement_unchanged":
|
|
19
21
|
return "Checkpoint made no progress";
|
|
20
22
|
case "summarization_failed":
|
|
@@ -1016,6 +1016,7 @@ export function buildTimeline(
|
|
|
1016
1016
|
case "tool.auth_needed":
|
|
1017
1017
|
case "credential.auth_needed": {
|
|
1018
1018
|
const capability = capabilityAuthorizationRequest(payload.capability);
|
|
1019
|
+
const setupRequest = customMcpSetupRequest(payload.setupRequest);
|
|
1019
1020
|
if (
|
|
1020
1021
|
event.type === "tool.auth_needed" &&
|
|
1021
1022
|
!stringValue(payload.toolName) &&
|
|
@@ -1045,11 +1046,12 @@ export function buildTimeline(
|
|
|
1045
1046
|
payload.connectionSubjectScope === "workspace"
|
|
1046
1047
|
? payload.connectionSubjectScope
|
|
1047
1048
|
: null,
|
|
1048
|
-
source:
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1049
|
+
source:
|
|
1050
|
+
capability || setupRequest
|
|
1051
|
+
? "capability"
|
|
1052
|
+
: event.type === "tool.auth_needed"
|
|
1053
|
+
? "tool"
|
|
1054
|
+
: "credential",
|
|
1053
1055
|
providerDomain: stringValue(payload.providerDomain),
|
|
1054
1056
|
connectionId: typeof payload.connectionId === "string" ? payload.connectionId : null,
|
|
1055
1057
|
authoritySource: payload.authoritySource === "host" ? "host" : null,
|
|
@@ -1064,6 +1066,7 @@ export function buildTimeline(
|
|
|
1064
1066
|
authorizationUrl:
|
|
1065
1067
|
typeof payload.authorizationUrl === "string" ? payload.authorizationUrl : null,
|
|
1066
1068
|
capability,
|
|
1069
|
+
setupRequest,
|
|
1067
1070
|
occurredAt: event.occurredAt,
|
|
1068
1071
|
});
|
|
1069
1072
|
break;
|
|
@@ -2623,6 +2626,29 @@ function capabilityAuthorizationRequest(
|
|
|
2623
2626
|
};
|
|
2624
2627
|
}
|
|
2625
2628
|
|
|
2629
|
+
function customMcpSetupRequest(value: unknown): NonNullable<AuthNeededItem["setupRequest"]> | null {
|
|
2630
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2631
|
+
const input = value as Record<string, unknown>;
|
|
2632
|
+
if (
|
|
2633
|
+
input.kind !== "mcp" ||
|
|
2634
|
+
typeof input.name !== "string" ||
|
|
2635
|
+
typeof input.rationale !== "string" ||
|
|
2636
|
+
typeof input.endpointUrl !== "string"
|
|
2637
|
+
)
|
|
2638
|
+
return null;
|
|
2639
|
+
try {
|
|
2640
|
+
if (new URL(input.endpointUrl).protocol !== "https:") return null;
|
|
2641
|
+
} catch {
|
|
2642
|
+
return null;
|
|
2643
|
+
}
|
|
2644
|
+
return {
|
|
2645
|
+
kind: "mcp",
|
|
2646
|
+
name: input.name,
|
|
2647
|
+
endpointUrl: input.endpointUrl,
|
|
2648
|
+
rationale: input.rationale,
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2626
2652
|
function stringList(value: unknown): string[] {
|
|
2627
2653
|
return Array.isArray(value)
|
|
2628
2654
|
? value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
|
package/src/timeline/types.ts
CHANGED
|
@@ -475,6 +475,8 @@ export type AuthNeededItem = {
|
|
|
475
475
|
authorizationUrl: string | null;
|
|
476
476
|
/** Agent-selected catalog recommendation. The host still owns authorization. */
|
|
477
477
|
capability?: NonNullable<ToolAuthNeededPayload["capability"]> | null | undefined;
|
|
478
|
+
/** Human-reviewed custom integration proposal. Never treated as catalog authority. */
|
|
479
|
+
setupRequest?: ToolAuthNeededPayload["setupRequest"] | null | undefined;
|
|
478
480
|
occurredAt: string;
|
|
479
481
|
};
|
|
480
482
|
|