@opengeni/react 1.4.0 → 1.4.4
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 +8 -0
- package/dist/accounts.js +2 -0
- package/dist/accounts.js.map +1 -1
- package/dist/artifacts-document.js +5 -4
- package/dist/artifacts-presentation.js +5 -4
- package/dist/artifacts-spreadsheet.js +4 -3
- package/dist/artifacts.js +14 -13
- package/dist/artifacts.js.map +1 -1
- package/dist/browser-viewer-5EEAS3IU.js +17 -0
- package/dist/chat-interactive-block-ZFHOKTMS.js +12 -0
- package/dist/{chunk-WEHMPOXK.js → chunk-2DDJ6YH6.js} +3 -1
- package/dist/{chunk-WEHMPOXK.js.map → chunk-2DDJ6YH6.js.map} +1 -1
- package/dist/{chunk-C5X5AJXK.js → chunk-2HVLQ26H.js} +5 -4
- package/dist/{chunk-C5X5AJXK.js.map → chunk-2HVLQ26H.js.map} +1 -1
- package/dist/{chunk-HDJWDK7W.js → chunk-2OQA3CPB.js} +300 -166
- package/dist/{chunk-HDJWDK7W.js.map → chunk-2OQA3CPB.js.map} +1 -1
- package/dist/{chunk-A4UJB2MM.js → chunk-32CGLXW2.js} +4 -3
- package/dist/{chunk-A4UJB2MM.js.map → chunk-32CGLXW2.js.map} +1 -1
- package/dist/{chunk-5CVXZA3Y.js → chunk-45NUOSXQ.js} +3 -2
- package/dist/{chunk-5CVXZA3Y.js.map → chunk-45NUOSXQ.js.map} +1 -1
- package/dist/{chunk-WUISZ3WU.js → chunk-53KJBGPZ.js} +5 -4
- package/dist/{chunk-WUISZ3WU.js.map → chunk-53KJBGPZ.js.map} +1 -1
- package/dist/{chunk-MBMPHOMA.js → chunk-55DXD22O.js} +3 -2
- package/dist/{chunk-MBMPHOMA.js.map → chunk-55DXD22O.js.map} +1 -1
- package/dist/{chunk-JT4JW7BC.js → chunk-5I4VRWBB.js} +3 -1
- package/dist/{chunk-JT4JW7BC.js.map → chunk-5I4VRWBB.js.map} +1 -1
- package/dist/{chunk-UK4TINKT.js → chunk-B27E3V37.js} +3 -1
- package/dist/{chunk-UK4TINKT.js.map → chunk-B27E3V37.js.map} +1 -1
- package/dist/{chunk-J6DBS4T3.js → chunk-BNKV5ZLF.js} +5 -4
- package/dist/{chunk-J6DBS4T3.js.map → chunk-BNKV5ZLF.js.map} +1 -1
- package/dist/{chunk-YM2DE7BQ.js → chunk-C5BF4L26.js} +13 -12
- package/dist/{chunk-YM2DE7BQ.js.map → chunk-C5BF4L26.js.map} +1 -1
- package/dist/{chunk-YBXQDQFX.js → chunk-C7CMD56T.js} +6 -5
- package/dist/{chunk-YBXQDQFX.js.map → chunk-C7CMD56T.js.map} +1 -1
- package/dist/{chunk-UGLEYEMP.js → chunk-CM7LVUKC.js} +3 -1
- package/dist/{chunk-UGLEYEMP.js.map → chunk-CM7LVUKC.js.map} +1 -1
- package/dist/{chunk-JVYQRRC4.js → chunk-CVYCLFMH.js} +3 -1
- package/dist/{chunk-JVYQRRC4.js.map → chunk-CVYCLFMH.js.map} +1 -1
- package/dist/{chunk-LJ2UTJJE.js → chunk-EAOSEIXB.js} +182 -97
- package/dist/chunk-EAOSEIXB.js.map +1 -0
- package/dist/{chunk-Y2PRMYLC.js → chunk-G3U2A6GS.js} +3 -2
- package/dist/{chunk-Y2PRMYLC.js.map → chunk-G3U2A6GS.js.map} +1 -1
- package/dist/{chunk-WZMYFJD3.js → chunk-G6YRCIYJ.js} +16 -7
- package/dist/{chunk-WZMYFJD3.js.map → chunk-G6YRCIYJ.js.map} +1 -1
- package/dist/{chunk-B3V6TG77.js → chunk-GC3PH6N7.js} +3 -1
- package/dist/{chunk-B3V6TG77.js.map → chunk-GC3PH6N7.js.map} +1 -1
- package/dist/{chunk-CJUO7J5Z.js → chunk-HXYOSSQO.js} +3 -2
- package/dist/{chunk-CJUO7J5Z.js.map → chunk-HXYOSSQO.js.map} +1 -1
- package/dist/{chunk-GYVRFZDY.js → chunk-I6OY3MWC.js} +310 -115
- package/dist/chunk-I6OY3MWC.js.map +1 -0
- package/dist/{chunk-BRM6FVZA.js → chunk-IEE7WCLN.js} +4 -3
- package/dist/{chunk-BRM6FVZA.js.map → chunk-IEE7WCLN.js.map} +1 -1
- package/dist/{chunk-DYWBUX5Q.js → chunk-IMSUVYQB.js} +6 -5
- package/dist/{chunk-DYWBUX5Q.js.map → chunk-IMSUVYQB.js.map} +1 -1
- package/dist/{chunk-LN6TCXD3.js → chunk-IUHHPMAX.js} +184 -48
- package/dist/chunk-IUHHPMAX.js.map +1 -0
- package/dist/{chunk-SGKD2OCN.js → chunk-IYQCWGU5.js} +6 -5
- package/dist/{chunk-SGKD2OCN.js.map → chunk-IYQCWGU5.js.map} +1 -1
- package/dist/{chunk-J2QWVSQY.js → chunk-JH7V627O.js} +8 -7
- package/dist/{chunk-J2QWVSQY.js.map → chunk-JH7V627O.js.map} +1 -1
- package/dist/{chunk-EESD3PY3.js → chunk-JHTIMBOL.js} +6 -5
- package/dist/{chunk-EESD3PY3.js.map → chunk-JHTIMBOL.js.map} +1 -1
- package/dist/{chunk-LNIRAOA3.js → chunk-L2J3PU7A.js} +7 -6
- package/dist/{chunk-LNIRAOA3.js.map → chunk-L2J3PU7A.js.map} +1 -1
- package/dist/{chunk-ZYRGEKWS.js → chunk-LJUMYUBO.js} +3 -1
- package/dist/{chunk-ZYRGEKWS.js.map → chunk-LJUMYUBO.js.map} +1 -1
- package/dist/{chunk-4RVYOY4O.js → chunk-LSW2OJVV.js} +4 -3
- package/dist/{chunk-4RVYOY4O.js.map → chunk-LSW2OJVV.js.map} +1 -1
- package/dist/{chunk-UTRTG6LH.js → chunk-LVS3MH5T.js} +3 -1
- package/dist/{chunk-UTRTG6LH.js.map → chunk-LVS3MH5T.js.map} +1 -1
- package/dist/chunk-M357MHH3.js +2 -0
- package/dist/{chunk-WRHIZYZF.js → chunk-MEQM4LI3.js} +6 -5
- package/dist/{chunk-WRHIZYZF.js.map → chunk-MEQM4LI3.js.map} +1 -1
- package/dist/chunk-NX4ADH73.js +14 -0
- package/dist/chunk-NX4ADH73.js.map +1 -0
- package/dist/{chunk-XCHWDJ22.js → chunk-OGTTUYNT.js} +3 -1
- package/dist/{chunk-XCHWDJ22.js.map → chunk-OGTTUYNT.js.map} +1 -1
- package/dist/{chunk-GIMRELBW.js → chunk-OYQQJL3J.js} +3 -1
- package/dist/{chunk-GIMRELBW.js.map → chunk-OYQQJL3J.js.map} +1 -1
- package/dist/{chunk-RSI7XYXL.js → chunk-PBCZL4JP.js} +6 -5
- package/dist/{chunk-RSI7XYXL.js.map → chunk-PBCZL4JP.js.map} +1 -1
- package/dist/{chunk-I7IWRQLN.js → chunk-PHBFJCEU.js} +17 -11
- package/dist/chunk-PHBFJCEU.js.map +1 -0
- package/dist/{chunk-3CTRFMAK.js → chunk-PKCAYHDC.js} +5 -4
- package/dist/{chunk-3CTRFMAK.js.map → chunk-PKCAYHDC.js.map} +1 -1
- package/dist/{chunk-K3HQAKQX.js → chunk-PMV53IMM.js} +3 -1
- package/dist/{chunk-K3HQAKQX.js.map → chunk-PMV53IMM.js.map} +1 -1
- package/dist/{chunk-YQEAA6LT.js → chunk-QHUE7E4N.js} +3 -2
- package/dist/{chunk-YQEAA6LT.js.map → chunk-QHUE7E4N.js.map} +1 -1
- package/dist/{chunk-OA6HFRQP.js → chunk-QMMCJGFI.js} +4 -3
- package/dist/{chunk-OA6HFRQP.js.map → chunk-QMMCJGFI.js.map} +1 -1
- package/dist/{chunk-YV4L3ILF.js → chunk-QV3DJVRZ.js} +3 -1
- package/dist/{chunk-YV4L3ILF.js.map → chunk-QV3DJVRZ.js.map} +1 -1
- package/dist/{chunk-447JLCBU.js → chunk-S2QH6EZ5.js} +3 -1
- package/dist/{chunk-447JLCBU.js.map → chunk-S2QH6EZ5.js.map} +1 -1
- package/dist/{chunk-PNY3JKBJ.js → chunk-SFH43JT2.js} +5 -4
- package/dist/{chunk-PNY3JKBJ.js.map → chunk-SFH43JT2.js.map} +1 -1
- package/dist/{chunk-AWZLIMKX.js → chunk-T46QZH3E.js} +3 -1
- package/dist/{chunk-AWZLIMKX.js.map → chunk-T46QZH3E.js.map} +1 -1
- package/dist/{chunk-5J4FOW6Q.js → chunk-TNEZNMSN.js} +3 -1
- package/dist/{chunk-5J4FOW6Q.js.map → chunk-TNEZNMSN.js.map} +1 -1
- package/dist/{chunk-22KP6LR5.js → chunk-TXS65BB2.js} +3 -1
- package/dist/{chunk-22KP6LR5.js.map → chunk-TXS65BB2.js.map} +1 -1
- package/dist/{chunk-H5X55XH7.js → chunk-UDPH5SLV.js} +4 -3
- package/dist/{chunk-H5X55XH7.js.map → chunk-UDPH5SLV.js.map} +1 -1
- package/dist/{chunk-VZTQ73XT.js → chunk-XEA5BCMA.js} +3 -1
- package/dist/{chunk-VZTQ73XT.js.map → chunk-XEA5BCMA.js.map} +1 -1
- package/dist/{chunk-5F4WR7X7.js → chunk-XS67YG5R.js} +3 -1
- package/dist/{chunk-5F4WR7X7.js.map → chunk-XS67YG5R.js.map} +1 -1
- package/dist/{chunk-TKOTKRWY.js → chunk-XTD7TZHW.js} +3 -1
- package/dist/{chunk-TKOTKRWY.js.map → chunk-XTD7TZHW.js.map} +1 -1
- package/dist/{chunk-A2PI6UMY.js → chunk-YSRBFJMQ.js} +3 -1
- package/dist/{chunk-A2PI6UMY.js.map → chunk-YSRBFJMQ.js.map} +1 -1
- package/dist/chunk-YVFRBXST.js +1420 -0
- package/dist/chunk-YVFRBXST.js.map +1 -0
- package/dist/{chunk-SCOTPE2I.js → chunk-YYLBXXTE.js} +3 -1
- package/dist/{chunk-SCOTPE2I.js.map → chunk-YYLBXXTE.js.map} +1 -1
- package/dist/{chunk-RHREZ7BO.js → chunk-ZUWF4NF7.js} +3 -1
- package/dist/{chunk-RHREZ7BO.js.map → chunk-ZUWF4NF7.js.map} +1 -1
- package/dist/clipboard.js +3 -2
- package/dist/components/composer-transcription-control.d.ts +4 -0
- package/dist/components/session-conversation.d.ts +6 -0
- package/dist/composer-voice-events.d.ts +8 -0
- package/dist/composer.js +15 -13
- package/dist/computer-viewer-ELNLZGHI.js +20 -0
- package/dist/connect.js +15 -12
- package/dist/connect.js.map +1 -1
- package/dist/desktop.js +6 -5
- package/dist/desktop.js.map +1 -1
- package/dist/diffs.js +2 -1
- package/dist/diffs.js.map +1 -1
- package/dist/editor.js +4 -3
- package/dist/editor.js.map +1 -1
- package/dist/embedded-voice-NIL4RDX3.js +69 -0
- package/dist/embedded-voice-NIL4RDX3.js.map +1 -0
- package/dist/{fleet-decision-row-YZ3ZKWMM.js → fleet-decision-row-SI63O5V4.js} +3 -1
- package/dist/{fleet-decision-row-YZ3ZKWMM.js.map → fleet-decision-row-SI63O5V4.js.map} +1 -1
- package/dist/hooks/use-client-config-flags.d.ts +6 -0
- package/dist/hooks/use-computer-session.d.ts +2 -0
- package/dist/hooks/use-realtime-voice-models.d.ts +9 -0
- package/dist/hooks/use-voice-input.d.ts +8 -1
- package/dist/index.js +47 -45
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +14 -13
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-GPYM2JNU.js → latest-question-2POFPYNB.js} +5 -4
- package/dist/{latest-question-GPYM2JNU.js.map → latest-question-2POFPYNB.js.map} +1 -1
- package/dist/machines.js +8 -7
- package/dist/{markdown-table-layout-QXSLK4BY.js → markdown-table-layout-DQ4DRNVT.js} +3 -1
- package/dist/{markdown-table-layout-QXSLK4BY.js.map → markdown-table-layout-DQ4DRNVT.js.map} +1 -1
- package/dist/model-policy-picker-menu-WRNSRFQC.js +15 -0
- package/dist/model-policy.js +2 -1
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js → noto-sans-arabic-loader-XSH2KYWJ.js} +3 -1
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js.map → noto-sans-arabic-loader-XSH2KYWJ.js.map} +1 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js → noto-sans-jp-loader.generated-MR5L6V7K.js} +3 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js.map → noto-sans-jp-loader.generated-MR5L6V7K.js.map} +1 -1
- package/dist/{platform-activity-row-627BFZ46.js → platform-activity-row-PJJVATTZ.js} +3 -1
- package/dist/{platform-activity-row-627BFZ46.js.map → platform-activity-row-PJJVATTZ.js.map} +1 -1
- package/dist/{queue-surface-implementation-ZHFI5JBP.js → queue-surface-implementation-67FS7FUI.js} +10 -9
- package/dist/{queue-surface-implementation-ZHFI5JBP.js.map → queue-surface-implementation-67FS7FUI.js.map} +1 -1
- package/dist/realtime/embedded-voice.d.ts +20 -0
- package/dist/realtime.js +22 -1353
- package/dist/realtime.js.map +1 -1
- package/dist/sandbox-files-34CJJNOY.js +21 -0
- package/dist/session-ui.js +32 -30
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +10 -9
- package/dist/sites.js +2 -1
- package/dist/terminal.js +6 -5
- package/dist/terminal.js.map +1 -1
- package/dist/{timeline-annotation-markers-GNDHTGBD.js → timeline-annotation-markers-U3G66KUD.js} +5 -4
- package/dist/{timeline-annotation-markers-GNDHTGBD.js.map → timeline-annotation-markers-U3G66KUD.js.map} +1 -1
- package/dist/{timeline-annotation-selection-DMLMM2TX.js → timeline-annotation-selection-BI7QTPTZ.js} +4 -3
- package/dist/{timeline-annotation-selection-DMLMM2TX.js.map → timeline-annotation-selection-BI7QTPTZ.js.map} +1 -1
- package/dist/{timeline-annotations-dialog-ELRDBHTS.js → timeline-annotations-dialog-FOMHSDTE.js} +7 -6
- package/dist/{timeline-annotations-dialog-ELRDBHTS.js.map → timeline-annotations-dialog-FOMHSDTE.js.map} +1 -1
- package/dist/usage.js +6 -5
- package/dist/usage.js.map +1 -1
- package/dist/voice-recording-owner.d.ts +7 -0
- package/package.json +3 -3
- package/src/components/composer-transcription-control.tsx +109 -27
- package/src/components/computer-viewer.tsx +139 -30
- package/src/components/markdown.tsx +7 -2
- package/src/components/message-timeline.tsx +10 -1
- package/src/components/session-conversation.tsx +44 -0
- package/src/components/session-list.tsx +1 -1
- package/src/components/session-proxy-scope.tsx +26 -5
- package/src/composer-voice-events.ts +12 -0
- package/src/connection-discovery.tsx +4 -1
- package/src/hooks/use-browser-session.ts +445 -168
- package/src/hooks/use-client-config-flags.ts +8 -0
- package/src/hooks/use-computer-session.ts +334 -98
- package/src/hooks/use-goal.ts +12 -1
- package/src/hooks/use-realtime-voice-models.ts +67 -0
- package/src/hooks/use-voice-input.ts +109 -18
- package/src/realtime/embedded-voice.tsx +81 -0
- package/src/realtime/realtime-control.tsx +82 -17
- package/src/voice-recording-owner.ts +22 -0
- package/styles/compiled.css +77 -18
- package/styles/index.css +49 -0
- package/styles/responsive.css +61 -4
- package/styles/session-capability.css +1 -1
- package/dist/browser-viewer-SHYKQRNX.js +0 -16
- package/dist/chat-interactive-block-UGBTNB3L.js +0 -11
- package/dist/chunk-GYVRFZDY.js.map +0 -1
- package/dist/chunk-I7IWRQLN.js.map +0 -1
- package/dist/chunk-IWIDV7SB.js +0 -1
- package/dist/chunk-LJ2UTJJE.js.map +0 -1
- package/dist/chunk-LN6TCXD3.js.map +0 -1
- package/dist/computer-viewer-6SRGWRUX.js +0 -19
- package/dist/model-policy-picker-menu-DXHUL73H.js +0 -14
- package/dist/sandbox-files-GORZOUUJ.js +0 -20
- /package/dist/{browser-viewer-SHYKQRNX.js.map → browser-viewer-5EEAS3IU.js.map} +0 -0
- /package/dist/{chat-interactive-block-UGBTNB3L.js.map → chat-interactive-block-ZFHOKTMS.js.map} +0 -0
- /package/dist/{chunk-IWIDV7SB.js.map → chunk-M357MHH3.js.map} +0 -0
- /package/dist/{computer-viewer-6SRGWRUX.js.map → computer-viewer-ELNLZGHI.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-DXHUL73H.js.map → model-policy-picker-menu-WRNSRFQC.js.map} +0 -0
- /package/dist/{sandbox-files-GORZOUUJ.js.map → sandbox-files-34CJJNOY.js.map} +0 -0
|
@@ -83,6 +83,16 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
83
83
|
const { client, workspaceId } = useEmbeddedBrowserInteraction(options);
|
|
84
84
|
const browserSessionId = options.browserSessionId;
|
|
85
85
|
const enabled = (options.enabled ?? true) && browserSessionId !== null;
|
|
86
|
+
const sourceRef = useRef({ client, workspaceId, browserSessionId, enabled });
|
|
87
|
+
if (
|
|
88
|
+
sourceRef.current.client !== client ||
|
|
89
|
+
sourceRef.current.workspaceId !== workspaceId ||
|
|
90
|
+
sourceRef.current.browserSessionId !== browserSessionId ||
|
|
91
|
+
sourceRef.current.enabled !== enabled
|
|
92
|
+
) {
|
|
93
|
+
sourceRef.current = { client, workspaceId, browserSessionId, enabled };
|
|
94
|
+
}
|
|
95
|
+
const source = sourceRef.current;
|
|
86
96
|
const semanticObservationEnabled = options.semanticObservationEnabled ?? true;
|
|
87
97
|
const semanticObservationEnabledRef = useRef(semanticObservationEnabled);
|
|
88
98
|
semanticObservationEnabledRef.current = semanticObservationEnabled;
|
|
@@ -93,6 +103,7 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
93
103
|
const pageLive = usePageLiveActivity();
|
|
94
104
|
const pollIntervalMs = Math.max(750, options.pollIntervalMs ?? 2_000);
|
|
95
105
|
const [state, setState] = useState<{
|
|
106
|
+
source: typeof source;
|
|
96
107
|
browserSessionId: string | null;
|
|
97
108
|
session: BrowserSession | null;
|
|
98
109
|
targets: BrowserTarget[];
|
|
@@ -102,14 +113,19 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
102
113
|
mutating: boolean;
|
|
103
114
|
error: Error | null;
|
|
104
115
|
inputFailure: BrowserInputFailure | null;
|
|
105
|
-
|
|
116
|
+
actionResultOrder: number;
|
|
117
|
+
}>(() => ({ ...emptyState(browserSessionId, enabled), source }));
|
|
106
118
|
const visible =
|
|
107
|
-
state.
|
|
119
|
+
state.source === source && state.browserSessionId === browserSessionId
|
|
120
|
+
? state
|
|
121
|
+
: { ...emptyState(browserSessionId, enabled), source };
|
|
108
122
|
const refreshBlocked = isNonRetryableInteractionError(visible.error);
|
|
109
123
|
const inputFailureRef = useRef(visible.inputFailure);
|
|
110
124
|
inputFailureRef.current = visible.inputFailure;
|
|
111
125
|
const selectedTargetIdRef = useRef<string | null>(visible.selectedTargetId);
|
|
112
126
|
selectedTargetIdRef.current = visible.selectedTargetId;
|
|
127
|
+
const selectionRevisionRef = useRef(0);
|
|
128
|
+
const actionResultOrderRef = useRef({ next: 0, settled: 0 });
|
|
113
129
|
const selectedTargetRef = useRef<BrowserTarget | null>(
|
|
114
130
|
visible.targets.find((target) => target.id === visible.selectedTargetId) ?? null,
|
|
115
131
|
);
|
|
@@ -134,12 +150,20 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
134
150
|
});
|
|
135
151
|
const mutationRef = useRef<{
|
|
136
152
|
browserSessionId: string | null;
|
|
153
|
+
source: typeof source;
|
|
137
154
|
count: number;
|
|
155
|
+
needsInventoryReconcile: boolean;
|
|
138
156
|
}>({
|
|
139
157
|
browserSessionId,
|
|
158
|
+
source,
|
|
140
159
|
count: 0,
|
|
160
|
+
needsInventoryReconcile: false,
|
|
141
161
|
});
|
|
142
162
|
const mountedRef = useRef(true);
|
|
163
|
+
const isCurrentSource = useCallback(
|
|
164
|
+
() => mountedRef.current && sourceRef.current === source,
|
|
165
|
+
[source],
|
|
166
|
+
);
|
|
143
167
|
|
|
144
168
|
const invalidateRefresh = useCallback(() => {
|
|
145
169
|
const id = requestRef.current.id + 1;
|
|
@@ -148,8 +172,8 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
148
172
|
}, []);
|
|
149
173
|
|
|
150
174
|
const refreshInventory = useCallback(
|
|
151
|
-
async (checkInput = false) => {
|
|
152
|
-
if (!enabled || !browserSessionId) return;
|
|
175
|
+
async (checkInput = false, observeSelectedTarget = true) => {
|
|
176
|
+
if (!enabled || !browserSessionId || !isCurrentSource()) return;
|
|
153
177
|
const checkedInputFailure = inputFailureRef.current;
|
|
154
178
|
const id = requestRef.current.id + 1;
|
|
155
179
|
requestRef.current.controller?.abort();
|
|
@@ -164,9 +188,10 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
164
188
|
signal: controller.signal,
|
|
165
189
|
}),
|
|
166
190
|
]);
|
|
167
|
-
if (!
|
|
191
|
+
if (!isCurrentSource() || requestRef.current.id !== id) return;
|
|
168
192
|
const selected = chooseTarget(targetResponse.targets, selectedTargetIdRef.current);
|
|
169
193
|
selectedTargetIdRef.current = selected?.id ?? null;
|
|
194
|
+
selectedTargetRef.current = selected;
|
|
170
195
|
const previousObservation =
|
|
171
196
|
observationRef.current.browserSessionId === browserSessionId
|
|
172
197
|
? observationRef.current.observation
|
|
@@ -175,8 +200,13 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
175
200
|
selected && previousObservation && sameObservationTarget(previousObservation, selected)
|
|
176
201
|
? previousObservation
|
|
177
202
|
: null;
|
|
178
|
-
observationRef.current = {
|
|
203
|
+
observationRef.current = {
|
|
204
|
+
browserSessionId,
|
|
205
|
+
observation: retainedObservation,
|
|
206
|
+
};
|
|
179
207
|
setState((current) =>
|
|
208
|
+
isCurrentSource() &&
|
|
209
|
+
current.source === source &&
|
|
180
210
|
current.browserSessionId === browserSessionId
|
|
181
211
|
? {
|
|
182
212
|
...current,
|
|
@@ -198,7 +228,7 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
198
228
|
}
|
|
199
229
|
: current,
|
|
200
230
|
);
|
|
201
|
-
if (!selected || !semanticObservationEnabledRef.current) return;
|
|
231
|
+
if (!selected || !observeSelectedTarget || !semanticObservationEnabledRef.current) return;
|
|
202
232
|
try {
|
|
203
233
|
const observation = await client.observeBrowserTarget(
|
|
204
234
|
workspaceId,
|
|
@@ -206,17 +236,36 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
206
236
|
selected.id,
|
|
207
237
|
{ signal: controller.signal },
|
|
208
238
|
);
|
|
209
|
-
if (!
|
|
239
|
+
if (!isCurrentSource() || requestRef.current.id !== id) return;
|
|
240
|
+
const currentSelected = selectedTargetRef.current;
|
|
241
|
+
if (
|
|
242
|
+
observationRef.current.browserSessionId !== browserSessionId ||
|
|
243
|
+
currentSelected?.id !== selected.id ||
|
|
244
|
+
observation.browserSessionId !== browserSessionId ||
|
|
245
|
+
observation.target.id !== selected.id ||
|
|
246
|
+
observation.target.controllerGeneration !== currentSelected.controllerGeneration ||
|
|
247
|
+
observation.target.targetGeneration !== currentSelected.targetGeneration
|
|
248
|
+
)
|
|
249
|
+
return;
|
|
250
|
+
// Navigation may complete between the inventory and its selected-page
|
|
251
|
+
// observation. Keep both the selected target and input fence coherent.
|
|
252
|
+
selectedTargetRef.current = observation.target;
|
|
210
253
|
observationRef.current = { browserSessionId, observation };
|
|
211
254
|
lastObservationAtRef.current = Date.now();
|
|
212
255
|
setState((current) =>
|
|
256
|
+
isCurrentSource() &&
|
|
257
|
+
current.source === source &&
|
|
213
258
|
current.browserSessionId === browserSessionId &&
|
|
214
259
|
current.selectedTargetId === observation.target.id
|
|
215
|
-
? {
|
|
260
|
+
? {
|
|
261
|
+
...current,
|
|
262
|
+
observation,
|
|
263
|
+
targets: replaceTarget(current.targets, observation.target),
|
|
264
|
+
}
|
|
216
265
|
: current,
|
|
217
266
|
);
|
|
218
267
|
} catch (cause) {
|
|
219
|
-
if (controller.signal.aborted || !
|
|
268
|
+
if (controller.signal.aborted || !isCurrentSource() || requestRef.current.id !== id)
|
|
220
269
|
return;
|
|
221
270
|
// Target inventory and the media plane remain authoritative for the
|
|
222
271
|
// human-facing browser. A failed semantic snapshot must not blank or
|
|
@@ -224,9 +273,10 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
224
273
|
void cause;
|
|
225
274
|
}
|
|
226
275
|
} catch (cause) {
|
|
227
|
-
if (controller.signal.aborted || !
|
|
228
|
-
return;
|
|
276
|
+
if (controller.signal.aborted || !isCurrentSource() || requestRef.current.id !== id) return;
|
|
229
277
|
setState((current) =>
|
|
278
|
+
isCurrentSource() &&
|
|
279
|
+
current.source === source &&
|
|
230
280
|
current.browserSessionId === browserSessionId
|
|
231
281
|
? {
|
|
232
282
|
...current,
|
|
@@ -241,13 +291,13 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
241
291
|
}
|
|
242
292
|
}
|
|
243
293
|
},
|
|
244
|
-
[browserSessionId, client, enabled, workspaceId],
|
|
294
|
+
[browserSessionId, client, enabled, isCurrentSource, source, workspaceId],
|
|
245
295
|
);
|
|
246
296
|
|
|
247
297
|
const refresh = useCallback(async () => await refreshInventory(true), [refreshInventory]);
|
|
248
298
|
|
|
249
299
|
const refreshSemantic = useCallback(async (): Promise<void> => {
|
|
250
|
-
if (!enabled || !browserSessionId) return;
|
|
300
|
+
if (!enabled || !browserSessionId || !isCurrentSource()) return;
|
|
251
301
|
const target = selectedTargetRef.current;
|
|
252
302
|
if (!target) return;
|
|
253
303
|
const id = requestRef.current.id + 1;
|
|
@@ -261,12 +311,15 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
261
311
|
target.id,
|
|
262
312
|
{ signal: controller.signal },
|
|
263
313
|
);
|
|
264
|
-
if (!
|
|
314
|
+
if (!isCurrentSource() || requestRef.current.id !== id) return;
|
|
265
315
|
if (!sameObservationTarget(observation, selectedTargetRef.current)) return;
|
|
266
316
|
observationRef.current = { browserSessionId, observation };
|
|
267
317
|
lastObservationAtRef.current = Date.now();
|
|
268
318
|
setState((current) =>
|
|
269
|
-
|
|
319
|
+
isCurrentSource() &&
|
|
320
|
+
current.source === source &&
|
|
321
|
+
current.browserSessionId === browserSessionId &&
|
|
322
|
+
current.selectedTargetId === target.id
|
|
270
323
|
? { ...current, observation }
|
|
271
324
|
: current,
|
|
272
325
|
);
|
|
@@ -275,26 +328,28 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
275
328
|
} finally {
|
|
276
329
|
if (requestRef.current.id === id) requestRef.current = { id, controller: null };
|
|
277
330
|
}
|
|
278
|
-
}, [browserSessionId, client, enabled, workspaceId]);
|
|
331
|
+
}, [browserSessionId, client, enabled, isCurrentSource, source, workspaceId]);
|
|
279
332
|
|
|
280
333
|
useEffect(() => {
|
|
281
334
|
mountedRef.current = true;
|
|
282
335
|
if (!enabled) {
|
|
283
336
|
invalidateRefresh();
|
|
284
|
-
setState(emptyState(browserSessionId, false));
|
|
337
|
+
setState({ ...emptyState(browserSessionId, false), source });
|
|
285
338
|
return;
|
|
286
339
|
}
|
|
287
340
|
setState((current) =>
|
|
341
|
+
isCurrentSource() &&
|
|
342
|
+
current.source === source &&
|
|
288
343
|
current.browserSessionId === browserSessionId
|
|
289
344
|
? { ...current, loading: true }
|
|
290
|
-
: emptyState(browserSessionId, true),
|
|
345
|
+
: { ...emptyState(browserSessionId, true), source },
|
|
291
346
|
);
|
|
292
347
|
void refreshInventory();
|
|
293
348
|
return () => {
|
|
294
349
|
mountedRef.current = false;
|
|
295
350
|
invalidateRefresh();
|
|
296
351
|
};
|
|
297
|
-
}, [browserSessionId, enabled, invalidateRefresh, refreshInventory]);
|
|
352
|
+
}, [browserSessionId, enabled, invalidateRefresh, isCurrentSource, refreshInventory, source]);
|
|
298
353
|
|
|
299
354
|
useEffect(() => {
|
|
300
355
|
const previous = previousSemanticObservationRef.current;
|
|
@@ -321,11 +376,23 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
321
376
|
}
|
|
322
377
|
observationRef.current = { browserSessionId, observation: null };
|
|
323
378
|
setState((current) =>
|
|
324
|
-
|
|
379
|
+
isCurrentSource() &&
|
|
380
|
+
current.source === source &&
|
|
381
|
+
current.browserSessionId === browserSessionId
|
|
382
|
+
? { ...current, observation: null }
|
|
383
|
+
: current,
|
|
325
384
|
);
|
|
326
385
|
void refreshSemantic();
|
|
327
386
|
}
|
|
328
|
-
}, [
|
|
387
|
+
}, [
|
|
388
|
+
browserSessionId,
|
|
389
|
+
enabled,
|
|
390
|
+
isCurrentSource,
|
|
391
|
+
pollIntervalMs,
|
|
392
|
+
refreshSemantic,
|
|
393
|
+
semanticObservationEnabled,
|
|
394
|
+
source,
|
|
395
|
+
]);
|
|
329
396
|
|
|
330
397
|
useEffect(() => {
|
|
331
398
|
if (!enabled || !pageLive || visible.mutating || refreshBlocked) return;
|
|
@@ -339,6 +406,7 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
339
406
|
if (!enabled || !browserSessionId || !pageLive || refreshBlocked) return;
|
|
340
407
|
let disposed = false;
|
|
341
408
|
const heartbeat = () => {
|
|
409
|
+
if (!isCurrentSource()) return;
|
|
342
410
|
void client.heartbeatBrowserSession(workspaceId, browserSessionId).catch(() => {
|
|
343
411
|
if (!disposed) void refreshInventory();
|
|
344
412
|
});
|
|
@@ -348,167 +416,282 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
348
416
|
disposed = true;
|
|
349
417
|
clearInterval(timer);
|
|
350
418
|
};
|
|
351
|
-
}, [
|
|
419
|
+
}, [
|
|
420
|
+
browserSessionId,
|
|
421
|
+
client,
|
|
422
|
+
enabled,
|
|
423
|
+
isCurrentSource,
|
|
424
|
+
pageLive,
|
|
425
|
+
refreshInventory,
|
|
426
|
+
refreshBlocked,
|
|
427
|
+
workspaceId,
|
|
428
|
+
]);
|
|
352
429
|
|
|
353
430
|
const runMutation = useCallback(
|
|
354
|
-
async <T>(
|
|
431
|
+
async <T>(
|
|
432
|
+
scopeBrowserSessionId: string,
|
|
433
|
+
operation: (selectionRevision: number, readRequestId: number) => Promise<T>,
|
|
434
|
+
selectionRead = false,
|
|
435
|
+
changesInventory = false,
|
|
436
|
+
): Promise<T> => {
|
|
437
|
+
if (!enabled || !isCurrentSource()) {
|
|
438
|
+
throw new Error("The browser source is no longer selected.");
|
|
439
|
+
}
|
|
440
|
+
const selectionRevision = ++selectionRevisionRef.current;
|
|
355
441
|
invalidateRefresh();
|
|
356
|
-
|
|
442
|
+
const readRequestId = requestRef.current.id;
|
|
443
|
+
if (
|
|
444
|
+
mutationRef.current.browserSessionId !== scopeBrowserSessionId ||
|
|
445
|
+
mutationRef.current.source !== source
|
|
446
|
+
) {
|
|
357
447
|
mutationRef.current = {
|
|
358
448
|
browserSessionId: scopeBrowserSessionId,
|
|
449
|
+
source,
|
|
359
450
|
count: 0,
|
|
451
|
+
needsInventoryReconcile: false,
|
|
360
452
|
};
|
|
361
453
|
}
|
|
362
454
|
mutationRef.current.count += 1;
|
|
363
455
|
setState((current) =>
|
|
456
|
+
isCurrentSource() &&
|
|
457
|
+
current.source === source &&
|
|
364
458
|
current.browserSessionId === scopeBrowserSessionId
|
|
365
459
|
? { ...current, mutating: true, error: null }
|
|
366
460
|
: current,
|
|
367
461
|
);
|
|
368
462
|
try {
|
|
369
|
-
return await operation();
|
|
463
|
+
return await operation(selectionRevision, readRequestId);
|
|
370
464
|
} catch (cause) {
|
|
371
465
|
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
)
|
|
466
|
+
// Selection observations share refresh ordering; physical mutations
|
|
467
|
+
// keep their existing outcome admission.
|
|
468
|
+
if (!selectionRead || requestRef.current.id === readRequestId) {
|
|
469
|
+
setState((current) =>
|
|
470
|
+
isCurrentSource() &&
|
|
471
|
+
selectionRevisionRef.current === selectionRevision &&
|
|
472
|
+
current.source === source &&
|
|
473
|
+
current.browserSessionId === scopeBrowserSessionId
|
|
474
|
+
? { ...current, error }
|
|
475
|
+
: current,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
375
478
|
throw error;
|
|
376
479
|
} finally {
|
|
377
|
-
if (
|
|
480
|
+
if (
|
|
481
|
+
mutationRef.current.browserSessionId === scopeBrowserSessionId &&
|
|
482
|
+
mutationRef.current.source === source
|
|
483
|
+
) {
|
|
484
|
+
if (
|
|
485
|
+
changesInventory &&
|
|
486
|
+
(selectionRevisionRef.current !== selectionRevision ||
|
|
487
|
+
requestRef.current.id !== readRequestId)
|
|
488
|
+
) {
|
|
489
|
+
mutationRef.current.needsInventoryReconcile = true;
|
|
490
|
+
}
|
|
378
491
|
mutationRef.current.count = Math.max(0, mutationRef.current.count - 1);
|
|
379
492
|
const mutating = mutationRef.current.count > 0;
|
|
380
493
|
setState((current) =>
|
|
381
|
-
|
|
494
|
+
isCurrentSource() &&
|
|
495
|
+
current.source === source &&
|
|
496
|
+
current.browserSessionId === scopeBrowserSessionId
|
|
497
|
+
? { ...current, mutating }
|
|
498
|
+
: current,
|
|
382
499
|
);
|
|
500
|
+
if (!mutating && mutationRef.current.needsInventoryReconcile && isCurrentSource()) {
|
|
501
|
+
mutationRef.current.needsInventoryReconcile = false;
|
|
502
|
+
// A superseded tab mutation still changed the real inventory. Wait
|
|
503
|
+
// for all selections to settle, then reconcile without replacing
|
|
504
|
+
// the latest selected-page observation or cancelling its read.
|
|
505
|
+
void refreshInventory(false, false);
|
|
506
|
+
}
|
|
383
507
|
}
|
|
384
508
|
}
|
|
385
509
|
},
|
|
386
|
-
[invalidateRefresh],
|
|
510
|
+
[enabled, invalidateRefresh, isCurrentSource, refreshInventory, source],
|
|
387
511
|
);
|
|
388
512
|
|
|
389
513
|
const selectTarget = useCallback(
|
|
390
514
|
async (targetId: string): Promise<BrowserTarget> => {
|
|
391
515
|
if (!browserSessionId) throw new Error("No BrowserSession is selected.");
|
|
392
|
-
return await runMutation(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
observation
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
516
|
+
return await runMutation(
|
|
517
|
+
browserSessionId,
|
|
518
|
+
async (selectionRevision, readRequestId) => {
|
|
519
|
+
const isCurrentSelection = () =>
|
|
520
|
+
isCurrentSource() && selectionRevisionRef.current === selectionRevision;
|
|
521
|
+
// Once admitted, a queued observation survives immediate input.
|
|
522
|
+
const isCurrentSelectionRead = () =>
|
|
523
|
+
isCurrentSelection() && requestRef.current.id === readRequestId;
|
|
524
|
+
let targets = visible.targets;
|
|
525
|
+
let target: BrowserTarget;
|
|
526
|
+
let observation: BrowserObservation;
|
|
527
|
+
try {
|
|
528
|
+
observation = await client.selectBrowserTarget(workspaceId, browserSessionId, targetId);
|
|
529
|
+
target = observation.target;
|
|
530
|
+
} catch (cause) {
|
|
531
|
+
if (!isCurrentSelectionRead() || !isMissingBrowserTarget(cause)) throw cause;
|
|
532
|
+
// A physical/attached tab may disappear between inventory and click.
|
|
533
|
+
// Reconcile once from the authoritative controller inventory and move
|
|
534
|
+
// to its live selected/first page instead of leaving a dead tab ID in
|
|
535
|
+
// React state. Session-level 404s are deliberately not swallowed.
|
|
536
|
+
const response = await client.listBrowserTargets(workspaceId, browserSessionId);
|
|
537
|
+
if (!isCurrentSelectionRead()) throw cause;
|
|
538
|
+
targets = response.targets;
|
|
539
|
+
const fallback = chooseTarget(targets, null);
|
|
540
|
+
if (!fallback) {
|
|
541
|
+
selectedTargetIdRef.current = null;
|
|
542
|
+
selectedTargetRef.current = null;
|
|
543
|
+
observationRef.current = { browserSessionId, observation: null };
|
|
544
|
+
setState((current) =>
|
|
545
|
+
isCurrentSelection() &&
|
|
546
|
+
current.source === source &&
|
|
547
|
+
current.browserSessionId === browserSessionId
|
|
548
|
+
? {
|
|
549
|
+
...current,
|
|
550
|
+
targets,
|
|
551
|
+
selectedTargetId: null,
|
|
552
|
+
observation: null,
|
|
553
|
+
}
|
|
554
|
+
: current,
|
|
555
|
+
);
|
|
556
|
+
throw cause;
|
|
557
|
+
}
|
|
558
|
+
observation = await client.selectBrowserTarget(
|
|
559
|
+
workspaceId,
|
|
560
|
+
browserSessionId,
|
|
561
|
+
fallback.id,
|
|
420
562
|
);
|
|
421
|
-
|
|
563
|
+
target = observation.target;
|
|
422
564
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
565
|
+
if (!isCurrentSelectionRead()) return target;
|
|
566
|
+
selectedTargetIdRef.current = target.id;
|
|
567
|
+
selectedTargetRef.current = target;
|
|
568
|
+
observationRef.current = { browserSessionId, observation };
|
|
569
|
+
lastObservationAtRef.current = Date.now();
|
|
570
|
+
setState((current) =>
|
|
571
|
+
isCurrentSelection() &&
|
|
572
|
+
current.source === source &&
|
|
573
|
+
current.browserSessionId === browserSessionId
|
|
574
|
+
? {
|
|
575
|
+
...current,
|
|
576
|
+
targets: replaceTarget(targets, target).map((candidate) => ({
|
|
577
|
+
...candidate,
|
|
578
|
+
selected: candidate.id === target.id,
|
|
579
|
+
})),
|
|
580
|
+
selectedTargetId: target.id,
|
|
581
|
+
observation,
|
|
582
|
+
}
|
|
583
|
+
: current,
|
|
427
584
|
);
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
lastObservationAtRef.current = Date.now();
|
|
433
|
-
setState((current) =>
|
|
434
|
-
current.browserSessionId === browserSessionId
|
|
435
|
-
? {
|
|
436
|
-
...current,
|
|
437
|
-
targets: targets.map((candidate) => ({
|
|
438
|
-
...candidate,
|
|
439
|
-
selected: candidate.id === target.id,
|
|
440
|
-
})),
|
|
441
|
-
selectedTargetId: target.id,
|
|
442
|
-
observation,
|
|
443
|
-
}
|
|
444
|
-
: current,
|
|
445
|
-
);
|
|
446
|
-
return target;
|
|
447
|
-
});
|
|
585
|
+
return target;
|
|
586
|
+
},
|
|
587
|
+
true,
|
|
588
|
+
);
|
|
448
589
|
},
|
|
449
|
-
[browserSessionId, client, runMutation, visible.targets, workspaceId],
|
|
590
|
+
[browserSessionId, client, isCurrentSource, runMutation, source, visible.targets, workspaceId],
|
|
450
591
|
);
|
|
451
592
|
|
|
452
593
|
const openTarget = useCallback(
|
|
453
594
|
async (url?: string): Promise<BrowserTarget> => {
|
|
454
595
|
if (!browserSessionId) throw new Error("No BrowserSession is selected.");
|
|
455
|
-
return await runMutation(
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
596
|
+
return await runMutation(
|
|
597
|
+
browserSessionId,
|
|
598
|
+
async (selectionRevision, readRequestId) => {
|
|
599
|
+
const isCurrentSelection = () =>
|
|
600
|
+
isCurrentSource() && selectionRevisionRef.current === selectionRevision;
|
|
601
|
+
const isCurrentSelectionRead = () =>
|
|
602
|
+
isCurrentSelection() && requestRef.current.id === readRequestId;
|
|
603
|
+
const observation = await client.openBrowserTarget(
|
|
604
|
+
workspaceId,
|
|
605
|
+
browserSessionId,
|
|
606
|
+
url === undefined ? {} : { url },
|
|
607
|
+
);
|
|
608
|
+
const target = observation.target;
|
|
609
|
+
if (!isCurrentSource()) return target;
|
|
610
|
+
if (!isCurrentSelectionRead()) return target;
|
|
611
|
+
selectedTargetIdRef.current = target.id;
|
|
612
|
+
selectedTargetRef.current = target;
|
|
613
|
+
observationRef.current = { browserSessionId, observation };
|
|
614
|
+
lastObservationAtRef.current = Date.now();
|
|
615
|
+
setState((current) =>
|
|
616
|
+
isCurrentSelection() &&
|
|
617
|
+
current.source === source &&
|
|
618
|
+
current.browserSessionId === browserSessionId
|
|
619
|
+
? {
|
|
620
|
+
...current,
|
|
621
|
+
targets: [
|
|
622
|
+
...current.targets.map((candidate) => ({
|
|
623
|
+
...candidate,
|
|
624
|
+
selected: false,
|
|
625
|
+
})),
|
|
626
|
+
target,
|
|
627
|
+
],
|
|
628
|
+
selectedTargetId: target.id,
|
|
629
|
+
observation,
|
|
630
|
+
}
|
|
631
|
+
: current,
|
|
632
|
+
);
|
|
633
|
+
return target;
|
|
634
|
+
},
|
|
635
|
+
false,
|
|
636
|
+
true,
|
|
637
|
+
);
|
|
483
638
|
},
|
|
484
|
-
[browserSessionId, client, runMutation, workspaceId],
|
|
639
|
+
[browserSessionId, client, isCurrentSource, runMutation, source, workspaceId],
|
|
485
640
|
);
|
|
486
641
|
|
|
487
642
|
const closeTarget = useCallback(
|
|
488
643
|
async (targetId: string): Promise<void> => {
|
|
489
644
|
if (!browserSessionId) throw new Error("No BrowserSession is selected.");
|
|
490
|
-
await runMutation(
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
645
|
+
await runMutation(
|
|
646
|
+
browserSessionId,
|
|
647
|
+
async (selectionRevision, readRequestId) => {
|
|
648
|
+
const isCurrentSelection = () =>
|
|
649
|
+
isCurrentSource() && selectionRevisionRef.current === selectionRevision;
|
|
650
|
+
const isCurrentSelectionRead = () =>
|
|
651
|
+
isCurrentSelection() && requestRef.current.id === readRequestId;
|
|
652
|
+
const response = await client.closeBrowserTarget(workspaceId, browserSessionId, targetId);
|
|
653
|
+
if (!isCurrentSource()) return;
|
|
654
|
+
if (!isCurrentSelectionRead()) return;
|
|
655
|
+
const selected = chooseTarget(response.targets, selectedTargetIdRef.current);
|
|
656
|
+
const observed = selected
|
|
657
|
+
? await client.observeBrowserTarget(workspaceId, browserSessionId, selected.id)
|
|
658
|
+
: null;
|
|
659
|
+
if (!isCurrentSource()) return;
|
|
660
|
+
if (!isCurrentSelectionRead()) return;
|
|
661
|
+
const observation =
|
|
662
|
+
observed &&
|
|
663
|
+
selected &&
|
|
664
|
+
observed.browserSessionId === browserSessionId &&
|
|
665
|
+
observed.target.id === selected.id &&
|
|
666
|
+
observed.target.controllerGeneration === selected.controllerGeneration &&
|
|
667
|
+
observed.target.targetGeneration === selected.targetGeneration
|
|
668
|
+
? observed
|
|
669
|
+
: null;
|
|
670
|
+
const currentTarget = observation?.target ?? selected;
|
|
671
|
+
selectedTargetIdRef.current = currentTarget?.id ?? null;
|
|
672
|
+
selectedTargetRef.current = currentTarget;
|
|
673
|
+
observationRef.current = { browserSessionId, observation };
|
|
674
|
+
if (observation) lastObservationAtRef.current = Date.now();
|
|
675
|
+
setState((current) =>
|
|
676
|
+
isCurrentSelection() &&
|
|
677
|
+
current.source === source &&
|
|
678
|
+
current.browserSessionId === browserSessionId
|
|
679
|
+
? {
|
|
680
|
+
...current,
|
|
681
|
+
targets: currentTarget
|
|
682
|
+
? replaceTarget(response.targets, currentTarget)
|
|
683
|
+
: response.targets,
|
|
684
|
+
selectedTargetId: currentTarget?.id ?? null,
|
|
685
|
+
observation,
|
|
686
|
+
}
|
|
687
|
+
: current,
|
|
688
|
+
);
|
|
689
|
+
},
|
|
690
|
+
false,
|
|
691
|
+
true,
|
|
692
|
+
);
|
|
510
693
|
},
|
|
511
|
-
[browserSessionId, client, runMutation, workspaceId],
|
|
694
|
+
[browserSessionId, client, isCurrentSource, runMutation, source, workspaceId],
|
|
512
695
|
);
|
|
513
696
|
|
|
514
697
|
const dispatchAction = useCallback(
|
|
@@ -516,7 +699,10 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
516
699
|
action: BrowserAction | BrowserActionBatch,
|
|
517
700
|
operationId: string,
|
|
518
701
|
frame:
|
|
519
|
-
| (Pick<
|
|
702
|
+
| (Pick<
|
|
703
|
+
BrowserFrame,
|
|
704
|
+
"browserSessionId" | "controllerGeneration" | "targetId" | "targetGeneration"
|
|
705
|
+
> & {
|
|
520
706
|
frameId: string | null;
|
|
521
707
|
documentGeneration: string | null;
|
|
522
708
|
})
|
|
@@ -524,6 +710,12 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
524
710
|
observationMode: "none" | "input" = "none",
|
|
525
711
|
): Promise<BrowserActionReceipt> => {
|
|
526
712
|
if (!browserSessionId) throw new Error("No BrowserSession is selected.");
|
|
713
|
+
if (!enabled || !isCurrentSource()) {
|
|
714
|
+
throw new Error("The browser source is no longer selected.");
|
|
715
|
+
}
|
|
716
|
+
if (!selectedTargetRef.current) {
|
|
717
|
+
throw new Error("The browser page is not ready for input.");
|
|
718
|
+
}
|
|
527
719
|
if (frame && frame.browserSessionId !== browserSessionId) {
|
|
528
720
|
throw new Error("The displayed browser frame belongs to another BrowserSession.");
|
|
529
721
|
}
|
|
@@ -538,7 +730,7 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
538
730
|
expectedDocumentGeneration: frame.documentGeneration,
|
|
539
731
|
expectedFrameId: frame.frameId,
|
|
540
732
|
}
|
|
541
|
-
: currentObservation
|
|
733
|
+
: currentObservation && sameObservationTarget(currentObservation, selectedTargetRef.current)
|
|
542
734
|
? {
|
|
543
735
|
targetId: currentObservation.target.id,
|
|
544
736
|
expectedTargetGeneration: currentObservation.target.targetGeneration,
|
|
@@ -549,6 +741,32 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
549
741
|
if (!fence) {
|
|
550
742
|
throw new Error("The browser page is not ready for input.");
|
|
551
743
|
}
|
|
744
|
+
const expectedControllerGeneration =
|
|
745
|
+
frame?.controllerGeneration ?? currentObservation!.target.controllerGeneration;
|
|
746
|
+
const selectionRevision = selectionRevisionRef.current;
|
|
747
|
+
const resultOrder = ++actionResultOrderRef.current.next;
|
|
748
|
+
let resultDocumentGeneration = fence.expectedDocumentGeneration;
|
|
749
|
+
const isCurrentResultView = () => {
|
|
750
|
+
const selected = selectedTargetRef.current;
|
|
751
|
+
return (
|
|
752
|
+
isCurrentSource() &&
|
|
753
|
+
selectionRevisionRef.current === selectionRevision &&
|
|
754
|
+
selected?.browserSessionId === browserSessionId &&
|
|
755
|
+
selected.id === fence.targetId &&
|
|
756
|
+
selected.controllerGeneration === expectedControllerGeneration &&
|
|
757
|
+
selected.targetGeneration === fence.expectedTargetGeneration &&
|
|
758
|
+
(selected.documentGeneration === fence.expectedDocumentGeneration ||
|
|
759
|
+
selected.documentGeneration === resultDocumentGeneration)
|
|
760
|
+
);
|
|
761
|
+
};
|
|
762
|
+
const canProjectResult = () =>
|
|
763
|
+
isCurrentResultView() && resultOrder >= actionResultOrderRef.current.settled;
|
|
764
|
+
const settleResult = () => {
|
|
765
|
+
if (!canProjectResult()) return false;
|
|
766
|
+
actionResultOrderRef.current.settled = resultOrder;
|
|
767
|
+
invalidateRefresh();
|
|
768
|
+
return true;
|
|
769
|
+
};
|
|
552
770
|
try {
|
|
553
771
|
const receipt = await client.actInBrowser(workspaceId, browserSessionId, {
|
|
554
772
|
operationId,
|
|
@@ -556,42 +774,90 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
556
774
|
observationMode,
|
|
557
775
|
action,
|
|
558
776
|
});
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
777
|
+
resultDocumentGeneration =
|
|
778
|
+
receipt.observation?.target.documentGeneration ?? fence.expectedDocumentGeneration;
|
|
779
|
+
const settled =
|
|
780
|
+
receipt.state === "completed" ||
|
|
781
|
+
receipt.state === "failed" ||
|
|
782
|
+
receipt.state === "outcome_unknown";
|
|
783
|
+
// Keep the operation result available to its caller, but an older
|
|
784
|
+
// delivery cannot replace a newer settled view or input failure.
|
|
785
|
+
if (!canProjectResult() || (settled && !settleResult())) return receipt;
|
|
786
|
+
const receivedObservation = receipt.observation;
|
|
787
|
+
const selectedTarget = selectedTargetRef.current;
|
|
788
|
+
let projectedObservation: BrowserObservation | null = null;
|
|
789
|
+
if (
|
|
790
|
+
settled &&
|
|
791
|
+
receivedObservation &&
|
|
792
|
+
canProjectResult() &&
|
|
793
|
+
receipt.browserSessionId === browserSessionId &&
|
|
794
|
+
receipt.controllerGeneration === expectedControllerGeneration &&
|
|
795
|
+
receipt.targetId === fence.targetId &&
|
|
796
|
+
observationRef.current.browserSessionId === browserSessionId &&
|
|
797
|
+
receivedObservation.browserSessionId === browserSessionId &&
|
|
798
|
+
selectedTarget?.browserSessionId === browserSessionId &&
|
|
799
|
+
selectedTarget.id === fence.targetId &&
|
|
800
|
+
receivedObservation.target.id === fence.targetId &&
|
|
801
|
+
selectedTarget.controllerGeneration === expectedControllerGeneration &&
|
|
802
|
+
receivedObservation.target.controllerGeneration === expectedControllerGeneration &&
|
|
803
|
+
selectedTarget.targetGeneration === fence.expectedTargetGeneration &&
|
|
804
|
+
receivedObservation.target.targetGeneration === fence.expectedTargetGeneration &&
|
|
805
|
+
(selectedTarget.documentGeneration === fence.expectedDocumentGeneration ||
|
|
806
|
+
selectedTarget.documentGeneration === receivedObservation.target.documentGeneration)
|
|
807
|
+
) {
|
|
808
|
+
// A receipt updates its own selected page only. Its operation outcome
|
|
809
|
+
// remains available even when the human has moved to another page.
|
|
810
|
+
projectedObservation = receivedObservation;
|
|
570
811
|
observationRef.current = {
|
|
571
812
|
browserSessionId,
|
|
572
|
-
observation:
|
|
813
|
+
observation: receivedObservation,
|
|
573
814
|
};
|
|
815
|
+
selectedTargetRef.current = receivedObservation.target;
|
|
574
816
|
lastObservationAtRef.current = Date.now();
|
|
575
|
-
setState((current) =>
|
|
576
|
-
current.browserSessionId === browserSessionId
|
|
577
|
-
? {
|
|
578
|
-
...current,
|
|
579
|
-
observation: receipt.observation,
|
|
580
|
-
targets: replaceTarget(current.targets, receipt.observation!.target),
|
|
581
|
-
}
|
|
582
|
-
: current,
|
|
583
|
-
);
|
|
584
817
|
}
|
|
818
|
+
setState((current) =>
|
|
819
|
+
isCurrentResultView() &&
|
|
820
|
+
current.source === source &&
|
|
821
|
+
current.browserSessionId === browserSessionId &&
|
|
822
|
+
current.actionResultOrder <= resultOrder
|
|
823
|
+
? {
|
|
824
|
+
...current,
|
|
825
|
+
actionResultOrder: settled ? resultOrder : current.actionResultOrder,
|
|
826
|
+
error: receipt.state === "completed" ? null : current.error,
|
|
827
|
+
inputFailure:
|
|
828
|
+
receipt.state === "completed"
|
|
829
|
+
? current.inputFailure
|
|
830
|
+
: {
|
|
831
|
+
operationId: receipt.operationId,
|
|
832
|
+
state: receipt.state,
|
|
833
|
+
error: receipt.error,
|
|
834
|
+
},
|
|
835
|
+
...(projectedObservation
|
|
836
|
+
? {
|
|
837
|
+
observation: projectedObservation,
|
|
838
|
+
targets: replaceTarget(current.targets, projectedObservation.target),
|
|
839
|
+
}
|
|
840
|
+
: {}),
|
|
841
|
+
}
|
|
842
|
+
: current,
|
|
843
|
+
);
|
|
585
844
|
return receipt;
|
|
586
845
|
} catch (cause) {
|
|
587
846
|
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
588
|
-
|
|
589
|
-
current
|
|
590
|
-
|
|
847
|
+
if (settleResult()) {
|
|
848
|
+
setState((current) =>
|
|
849
|
+
isCurrentResultView() &&
|
|
850
|
+
current.source === source &&
|
|
851
|
+
current.browserSessionId === browserSessionId &&
|
|
852
|
+
current.actionResultOrder <= resultOrder
|
|
853
|
+
? { ...current, error, actionResultOrder: resultOrder }
|
|
854
|
+
: current,
|
|
855
|
+
);
|
|
856
|
+
}
|
|
591
857
|
throw error;
|
|
592
858
|
}
|
|
593
859
|
},
|
|
594
|
-
[browserSessionId, client, workspaceId],
|
|
860
|
+
[browserSessionId, client, enabled, invalidateRefresh, isCurrentSource, source, workspaceId],
|
|
595
861
|
);
|
|
596
862
|
|
|
597
863
|
const act = useCallback(
|
|
@@ -615,17 +881,20 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
615
881
|
|
|
616
882
|
const observeForInput = useCallback(async (): Promise<BrowserObservation> => {
|
|
617
883
|
const target = selectedTargetRef.current;
|
|
884
|
+
if (!enabled || !isCurrentSource()) {
|
|
885
|
+
throw new Error("The browser source is no longer selected.");
|
|
886
|
+
}
|
|
618
887
|
if (!browserSessionId || !target) throw new Error("No browser tab is selected.");
|
|
619
888
|
const observation = await client.observeBrowserTarget(workspaceId, browserSessionId, target.id);
|
|
620
889
|
if (
|
|
621
|
-
!
|
|
890
|
+
!isCurrentSource() ||
|
|
622
891
|
observation.browserSessionId !== browserSessionId ||
|
|
623
892
|
!sameObservationTarget(observation, selectedTargetRef.current)
|
|
624
893
|
) {
|
|
625
894
|
throw new Error("The browser page changed. Open the options again.");
|
|
626
895
|
}
|
|
627
896
|
return observation;
|
|
628
|
-
}, [browserSessionId, client, workspaceId]);
|
|
897
|
+
}, [browserSessionId, client, enabled, isCurrentSource, workspaceId]);
|
|
629
898
|
|
|
630
899
|
const actFromObservation = useCallback(
|
|
631
900
|
async (action: BrowserAction, observation: BrowserObservation) => {
|
|
@@ -634,6 +903,7 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
634
903
|
}
|
|
635
904
|
return await dispatchAction(action, crypto.randomUUID(), {
|
|
636
905
|
browserSessionId: observation.browserSessionId,
|
|
906
|
+
controllerGeneration: observation.target.controllerGeneration,
|
|
637
907
|
targetId: observation.target.id,
|
|
638
908
|
targetGeneration: observation.target.targetGeneration,
|
|
639
909
|
documentGeneration: observation.target.documentGeneration,
|
|
@@ -645,6 +915,9 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
645
915
|
|
|
646
916
|
const diagnostics = useCallback(
|
|
647
917
|
async (diagnosticOptions: BrowserDiagnosticsOptions = {}): Promise<BrowserDiagnosticBatch> => {
|
|
918
|
+
if (!enabled || !isCurrentSource()) {
|
|
919
|
+
throw new Error("The browser source is no longer selected.");
|
|
920
|
+
}
|
|
648
921
|
const targetId = selectedTargetIdRef.current;
|
|
649
922
|
if (!browserSessionId || !targetId) {
|
|
650
923
|
throw new Error("No browser tab is selected.");
|
|
@@ -656,17 +929,20 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
656
929
|
diagnosticOptions,
|
|
657
930
|
);
|
|
658
931
|
},
|
|
659
|
-
[browserSessionId, client, workspaceId],
|
|
932
|
+
[browserSessionId, client, enabled, isCurrentSource, workspaceId],
|
|
660
933
|
);
|
|
661
934
|
|
|
662
935
|
const readClipboard = useCallback(async (): Promise<BrowserClipboard> => {
|
|
936
|
+
if (!enabled || !isCurrentSource()) {
|
|
937
|
+
throw new Error("The browser source is no longer selected.");
|
|
938
|
+
}
|
|
663
939
|
if (!browserSessionId) throw new Error("No BrowserSession is selected.");
|
|
664
940
|
const clipboard = await client.readBrowserClipboard(workspaceId, browserSessionId);
|
|
665
|
-
if (clipboard.browserSessionId !== browserSessionId) {
|
|
941
|
+
if (!isCurrentSource() || clipboard.browserSessionId !== browserSessionId) {
|
|
666
942
|
throw new Error("Browser clipboard belongs to another BrowserSession.");
|
|
667
943
|
}
|
|
668
944
|
return clipboard;
|
|
669
|
-
}, [browserSessionId, client, workspaceId]);
|
|
945
|
+
}, [browserSessionId, client, enabled, isCurrentSource, workspaceId]);
|
|
670
946
|
|
|
671
947
|
return {
|
|
672
948
|
session: visible.session,
|
|
@@ -702,6 +978,7 @@ function emptyState(browserSessionId: string | null, loading: boolean) {
|
|
|
702
978
|
mutating: false,
|
|
703
979
|
error: null as Error | null,
|
|
704
980
|
inputFailure: null as BrowserInputFailure | null,
|
|
981
|
+
actionResultOrder: 0,
|
|
705
982
|
};
|
|
706
983
|
}
|
|
707
984
|
|