@openchambery/web 2.0.0-beta.4 → 2.0.0-beta.6
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/assets/{AgentAvatar-Q1SmFRx2.js → AgentAvatar-BBmQXYDq.js} +1 -1
- package/dist/assets/AssistantView-BpvbpZBC.js +3 -0
- package/dist/assets/{DiagramView-BW92schO.js → DiagramView-UwKDr8HS.js} +1 -1
- package/dist/assets/{FadeInOnReveal-BZmwl-zE.js → FadeInOnReveal-w_BQ7K7K.js} +1 -1
- package/dist/assets/Icon-B9kZgYZn.js +1 -0
- package/dist/assets/{MarkdownRenderer-Bv05iDZZ.js → MarkdownRenderer-C8tDsO5F.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-C81xqYib.js → MarkdownRendererImpl-DRZ1iTbg.js} +1 -1
- package/dist/assets/MarkstreamRendererImpl-fU6uhDTr.js +3 -0
- package/dist/assets/{MobileDetailNavigation-BMgIu7zc.js → MobileDetailNavigation-MXUDe0nG.js} +1 -1
- package/dist/assets/{MobileShareBridge-BOXGNHmC.js → MobileShareBridge-C7x4jOGb.js} +2 -2
- package/dist/assets/MobileSurface-BQSmXYJ_.js +2 -0
- package/dist/assets/{MultiRunWindow-B5At2Z95.js → MultiRunWindow-ClfxxgP5.js} +1 -1
- package/dist/assets/{OnboardingScreen-bsPLgz4r.js → OnboardingScreen-BxItVnnO.js} +1 -1
- package/dist/assets/{OverlayScrollbar-4wp4migh.js → OverlayScrollbar-B4e0QD05.js} +1 -1
- package/dist/assets/SettingsView-DtWgey4y.js +25 -0
- package/dist/assets/{SettingsWindow-CMtX2bMl.js → SettingsWindow-Du8y1EI8.js} +1 -1
- package/dist/assets/TerminalView-DL9qkQDj.js +8 -0
- package/dist/assets/{ToolOutputDialog-uH1ZZDSS.js → ToolOutputDialog-BXawDmmf.js} +1 -1
- package/dist/assets/{appThemeRegistry-DK7q2juD.js → appThemeRegistry-DsSahEsq.js} +1 -1
- package/dist/assets/{button-CcNJUxUd.js → button-CwXyux0a.js} +1 -1
- package/dist/assets/collapsible-BZ6_cYJd.js +1 -0
- package/dist/assets/context-menu-DHrRifPE.js +1 -0
- package/dist/assets/{desktopHosts-C6HrP0jq.js → desktopHosts-JGFqhlaM.js} +1 -1
- package/dist/assets/{desktopRelayRestore-DdVBp45U.js → desktopRelayRestore-CHEwrI93.js} +1 -1
- package/dist/assets/{diagram-CxTs9hCQ.js → diagram-BwnPNKWt.js} +1 -1
- package/dist/assets/{dialog-KMVL9QKF.js → dialog-Dp0lQv5K.js} +1 -1
- package/dist/assets/es-DicJ8lKD.js +15 -0
- package/dist/assets/fileMentionTouchSelection-PZQrywzZ.js +3 -0
- package/dist/assets/{fileQueries-CjdBRy8y.js → fileQueries-DN6gbLhM.js} +1 -1
- package/dist/assets/fr-lhgVcBW_.js +13 -0
- package/dist/assets/{gitApi-yuWg9BN4.js → gitApi-C1oGENfw.js} +4 -4
- package/dist/assets/{imageSource-9vQBq3zp.js → imageSource-BMztRu0c.js} +1 -1
- package/dist/assets/{input-CrIvk3HZ.js → input-Biw8aNzR.js} +1 -1
- package/dist/assets/insertionBoundaries-BmQh-tke.js +1 -0
- package/dist/assets/ja-B9EZwOcw.js +15 -0
- package/dist/assets/ko-Dxk-uJRb.js +15 -0
- package/dist/assets/main-BnyE5H4i.js +169 -0
- package/dist/assets/main-D2BzpnUt.css +1 -0
- package/dist/assets/main-DOgXHO44.js +2 -0
- package/dist/assets/{markdownHeightCache-BmXVY_gc.js → markdownHeightCache-BOOrGCuG.js} +1 -1
- package/dist/assets/markdownTheme-DM5h1i18.js +1 -0
- package/dist/assets/{mcpQueries-C7jknHV8.js → mcpQueries-D9Dyzcve.js} +1 -1
- package/dist/assets/miniChat-C7FecHIX.js +2 -0
- package/dist/assets/mobile-_zrfbam4.js +2 -0
- package/dist/assets/multirun-BLFbv4I9.js +4 -0
- package/dist/assets/persistence-D0y7226-.js +3 -0
- package/dist/assets/pl-CFhwWLJj.js +15 -0
- package/dist/assets/platform-D_c-T5nO.js +6 -0
- package/dist/assets/projectMeta-BCdCRuYS.js +3 -0
- package/dist/assets/pt-BR-Ba6GfZ6J.js +15 -0
- package/dist/assets/renderElectronMiniChatApp-QQq7dj-0.js +2 -0
- package/dist/assets/renderMobileApp-BVIkeTKi.js +28 -0
- package/dist/assets/{runtimeConfig-BEANkvOd.js → runtimeConfig-S9D4fVI5.js} +1 -1
- package/dist/assets/runtimeEndpointReset-B6eE14f9.js +149 -0
- package/dist/assets/sessionChangeSummary-DLo3Iv_g.js +2 -0
- package/dist/assets/{sessionLookup-34XFmP9C.js → sessionLookup-BXXTw54z.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-DyEV0Heg.js → sortable-tabs-strip-CTY_wG46.js} +1 -1
- package/dist/assets/src-lS1keDVV.css +2 -0
- package/dist/assets/store-DmowK-GO.js +15 -0
- package/dist/assets/{terminalApi-CXaBEoa_.js → terminalApi-Dhqqp4xi.js} +1 -1
- package/dist/assets/{textarea-BBOlRI9O.js → textarea-QxsmcBGj.js} +1 -1
- package/dist/assets/{tooltip-CPI4fr1a.js → tooltip-CpMLl5_k.js} +1 -1
- package/dist/assets/uk-ChchBzy6.js +15 -0
- package/dist/assets/{url-DRDQXjyX.js → url-CGbJbiJq.js} +1 -1
- package/dist/assets/{useAppFontEffects-CiDe42I5.js → useAppFontEffects-BDs0O2qK.js} +56 -51
- package/dist/assets/{useAssistantUIStore-asNG6K7n.js → useAssistantUIStore-Bz4tRflF.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-ojoBEglc.js +1 -0
- package/dist/assets/{useEffectiveDirectory-C_CouL5v.js → useEffectiveDirectory-B_nHyDMJ.js} +1 -1
- package/dist/assets/{useFontPreferences-B0oUZAiB.js → useFontPreferences-Xw1vSkmC.js} +1 -1
- package/dist/assets/useMobileNavigationStore-D_96D661.js +414 -0
- package/dist/assets/{useRuntimeAPIs-DwLtO0c_.js → useRuntimeAPIs-DSLXJybW.js} +1 -1
- package/dist/assets/useSessionAutoCleanup-Dw34f-EW.js +2 -0
- package/dist/assets/{useThemeSystem-BlNUWH47.js → useThemeSystem-DMzWVJaW.js} +1 -1
- package/dist/assets/useUIStore-syVmbuDA.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-CPNYz6Wf.js → useWorkerHighlightedLines-CNlfP7nA.js} +1 -1
- package/dist/assets/{vendor-base-ui-CDjyofeD.js → vendor-base-ui-CnHfUmDN.js} +1 -1
- package/dist/assets/{vendor-cmdk-BFH0UpXX.js → vendor-cmdk-CeAGfrLo.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-accessibility-BrR3AKvM.js → vendor-dnd-kit-accessibility-BY5LhhUB.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-core-CNfdvk6H.js → vendor-dnd-kit-core-CvWxUPdb.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-sortable-DQUaTeGj.js → vendor-dnd-kit-sortable-hHdP9lLb.js} +1 -1
- package/dist/assets/{vendor-framer-motion-4dogsUk3.js → vendor-framer-motion-DFy_BSVd.js} +1 -1
- package/dist/assets/{vendor-legendapp-list-BU7MdPZR.js → vendor-legendapp-list-WE32lwNk.js} +1 -1
- package/dist/assets/{vendor-markstream-react-qHC2FykU.js → vendor-markstream-react-DX55XLGO.js} +3 -3
- package/dist/assets/vendor-opencode-client-C12gfjag.js +12 -0
- package/dist/assets/{vendor-pierre-diffs-CUfqC003.js → vendor-pierre-diffs-DX7XTPG3.js} +1 -1
- package/dist/assets/{vendor-react-C94A0AHl.js → vendor-react-DLPElpoQ.js} +1 -1
- package/dist/assets/{vendor-react-drawio-FVlWqKVE.js → vendor-react-drawio-ECtEwvbc.js} +1 -1
- package/dist/assets/{vendor-reactuses-core-C2A3EjUi.js → vendor-reactuses-core-Dq5ocTF8.js} +1 -1
- package/dist/assets/{vendor-sonner-DNqHmUQs.js → vendor-sonner-DVzioF3d.js} +1 -1
- package/dist/assets/{vendor-stream-diffs-CwbKsmlv.js → vendor-stream-diffs-DWy3xSmp.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-query-D0iFK57C.js → vendor-tanstack-react-query-D7pys_c9.js} +1 -1
- package/dist/assets/{vendor-tanstack-react-virtual-B2kV6cnT.js → vendor-tanstack-react-virtual-tlDgH6PG.js} +1 -1
- package/dist/assets/{vendor-zustand-BqLKf6ZY.js → vendor-zustand-B4dlGEM2.js} +1 -1
- package/dist/assets/zh-CN-Z_bkUQk5.js +15 -0
- package/dist/assets/zh-TW-CZeTm7kd.js +15 -0
- package/dist/index.html +13 -13
- package/dist/mini-chat.html +13 -13
- package/dist/mobile.html +13 -13
- package/package.json +3 -2
- package/server/index.js +38 -7
- package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
- package/server/lib/browser-provider/contract.js +312 -0
- package/server/lib/browser-provider/host.js +421 -0
- package/server/lib/browser-provider/host.test.js +304 -0
- package/server/lib/browser-provider/routes.js +122 -0
- package/server/lib/browser-provider/routes.test.js +120 -0
- package/server/lib/browser-provider/selection-store.js +43 -0
- package/server/lib/browser-provider/selection-store.test.js +35 -0
- package/server/lib/notifications/DOCUMENTATION.md +3 -1
- package/server/lib/notifications/runtime.js +110 -26
- package/server/lib/notifications/runtime.test.js +92 -0
- package/server/lib/notifications/template-runtime.js +130 -38
- package/server/lib/notifications/template-runtime.test.js +96 -0
- package/server/lib/opencode/DOCUMENTATION.md +4 -0
- package/server/lib/opencode/ensure-cli.d.ts +8 -0
- package/server/lib/opencode/feature-routes-runtime.js +10 -0
- package/server/lib/opencode/feature-routes-runtime.test.js +7 -0
- package/server/lib/opencode/proxy.js +3 -1
- package/server/lib/opencode/proxy.test.js +3 -3
- package/server/lib/opencode/routes.js +30 -0
- package/server/lib/opencode/upgrade-screen.d.ts +30 -0
- package/server/lib/opencode/upgrade-screen.js +299 -0
- package/server/lib/opencode/upgrade-screen.test.js +196 -0
- package/server/lib/session-metadata/DOCUMENTATION.md +27 -17
- package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
- package/server/lib/session-metadata/opencode-session-record.js +316 -0
- package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
- package/server/lib/session-metadata/routes.js +5 -1
- package/server/lib/session-metadata/session-archive.js +1 -1
- package/server/lib/session-metadata/session-metadata-store.d.ts +2 -0
- package/server/lib/session-metadata/session-metadata-store.js +63 -16
- package/server/lib/session-metadata/session-projection.js +26 -2
- package/server/lib/session-title/runtime.js +2 -2
- package/dist/assets/AssistantView-CwXkiCAd.js +0 -3
- package/dist/assets/Icon-AQPvxMiU.js +0 -1
- package/dist/assets/MarkstreamRendererImpl-CXUCQuKJ.js +0 -3
- package/dist/assets/MobileSurface-xuc_z0_G.js +0 -2
- package/dist/assets/SettingsView-CJ6tcH5_.js +0 -25
- package/dist/assets/TerminalView-Dvr-ysTS.js +0 -8
- package/dist/assets/collapsible-ZmZ6rLWs.js +0 -3
- package/dist/assets/context-menu-B2LXJgXL.js +0 -1
- package/dist/assets/es-CN3E_zXj.js +0 -15
- package/dist/assets/fileMentionTouchSelection-7RIwlpkB.js +0 -4
- package/dist/assets/fr-9zZvQZD2.js +0 -13
- package/dist/assets/insertionBoundaries-BiUCSkx-.js +0 -1
- package/dist/assets/ja-Dnjk_82N.js +0 -15
- package/dist/assets/ko-BxCEE99C.js +0 -15
- package/dist/assets/main-C9mIBo77.js +0 -2
- package/dist/assets/main-DiL6w6KF.js +0 -169
- package/dist/assets/main-qSeeJRW1.css +0 -1
- package/dist/assets/markdownTheme-CKac3wKC.js +0 -1
- package/dist/assets/miniChat-CwtUaElU.js +0 -2
- package/dist/assets/mobile-DB-6--ws.js +0 -2
- package/dist/assets/multirun-TAsxFI8e.js +0 -4
- package/dist/assets/persistence-56kq8Z4V.js +0 -3
- package/dist/assets/pl-CNNsWnPM.js +0 -15
- package/dist/assets/platform-C13fMVEs.js +0 -6
- package/dist/assets/pt-BR-BZB2DJMp.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-CGj0MNRU.js +0 -2
- package/dist/assets/renderMobileApp-CGMMqbsO.js +0 -28
- package/dist/assets/runtimeEndpointReset-A_ZBUJFS.js +0 -147
- package/dist/assets/sessionChangeSummary-qOLrP9fw.js +0 -2
- package/dist/assets/src-ubCWXZhR.css +0 -2
- package/dist/assets/store-DLCiZaA6.js +0 -15
- package/dist/assets/uk-rwQrxeYM.js +0 -15
- package/dist/assets/useDesktopWindowControlsLayout-BlizAlOt.js +0 -1
- package/dist/assets/useMobileNavigationStore-DfPClaW-.js +0 -414
- package/dist/assets/useSessionAutoCleanup-DtrfbxAp.js +0 -2
- package/dist/assets/useUIStore-CQYKJnrg.js +0 -1
- package/dist/assets/vendor-opencode-client-BOe2qpQb.js +0 -12
- package/dist/assets/zh-CN-BvmBDnkD.js +0 -15
- package/dist/assets/zh-TW-9wcHTX0y.js +0 -15
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow contract for an installed extension that answers agent browser actions.
|
|
3
|
+
*
|
|
4
|
+
* This fork has no guest SDK and no `service.provides` platform. A declaration
|
|
5
|
+
* is recognized when `service.provides`, top-level `provides`, or
|
|
6
|
+
* `contributes.service.provides` includes `"browser"`. The first of those that
|
|
7
|
+
* is an array wins, so a service contribution is not overridden by a looser
|
|
8
|
+
* field. The host posts the same envelope upstream posts to `POST /browser-control`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const BROWSER_PROVIDER_ROLE = 'browser';
|
|
12
|
+
|
|
13
|
+
/** Path on an extension loopback the host posts every action to. */
|
|
14
|
+
export const BROWSER_PROVIDER_PATH = '/browser-control';
|
|
15
|
+
|
|
16
|
+
export const BROWSER_PROVIDERS_ROUTE = '/api/browser-providers';
|
|
17
|
+
export const BROWSER_PROVIDER_SELECTION_ROUTE = '/api/browser-providers/selection';
|
|
18
|
+
export const BROWSER_PROVIDER_ACTIONS_ROUTE = '/api/browser-providers/actions';
|
|
19
|
+
|
|
20
|
+
/** Same id rule the settings catalog parser accepts. */
|
|
21
|
+
const PROVIDER_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
22
|
+
const PROVIDER_NAME_MAX = 200;
|
|
23
|
+
|
|
24
|
+
const BROWSER_CONTROL_ACTIONS = [
|
|
25
|
+
'browser.open',
|
|
26
|
+
'browser.snapshot',
|
|
27
|
+
'browser.click',
|
|
28
|
+
'browser.type',
|
|
29
|
+
'browser.scroll',
|
|
30
|
+
'browser.back',
|
|
31
|
+
'browser.forward',
|
|
32
|
+
'browser.inspect',
|
|
33
|
+
'browser.capture',
|
|
34
|
+
'browser.resize',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const BROWSER_VIEWPORT_MODES = ['mobile', 'tablet', 'desktop', 'fill'];
|
|
38
|
+
const BROWSER_SCROLL_DIRECTIONS = ['up', 'down', 'top', 'bottom'];
|
|
39
|
+
|
|
40
|
+
export const BROWSER_PROVIDER_OPEN_TIMEOUT_MS = 45_000;
|
|
41
|
+
export const BROWSER_PROVIDER_ACTION_TIMEOUT_MS = 20_000;
|
|
42
|
+
/** A screenshot is the largest answer the host will accept. */
|
|
43
|
+
export const BROWSER_PROVIDER_RESPONSE_MAX = 12_000_000;
|
|
44
|
+
|
|
45
|
+
const CONTROL_ACTIONS = new Set(BROWSER_CONTROL_ACTIONS);
|
|
46
|
+
|
|
47
|
+
export class BrowserProviderError extends Error {
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} message
|
|
50
|
+
* @param {{ code: string, status: number }} details
|
|
51
|
+
*/
|
|
52
|
+
constructor(message, { code, status }) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = 'BrowserProviderError';
|
|
55
|
+
this.code = code;
|
|
56
|
+
this.status = status;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const isBrowserControlAction = (value) => (
|
|
61
|
+
typeof value === 'string' && CONTROL_ACTIONS.has(value)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Roles an extension claims. `null` means the object does not declare a
|
|
66
|
+
* provides list at all — that is not the same as declaring an empty one.
|
|
67
|
+
*
|
|
68
|
+
* @param {unknown} declaration
|
|
69
|
+
* @returns {unknown[] | null}
|
|
70
|
+
*/
|
|
71
|
+
const readDeclaredProvides = (declaration) => {
|
|
72
|
+
if (!declaration || typeof declaration !== 'object') return null;
|
|
73
|
+
const record = /** @type {{ service?: { provides?: unknown }, provides?: unknown, contributes?: { service?: { provides?: unknown } } }} */ (declaration);
|
|
74
|
+
if (Array.isArray(record.service?.provides)) return record.service.provides;
|
|
75
|
+
if (Array.isArray(record.provides)) return record.provides;
|
|
76
|
+
if (Array.isArray(record.contributes?.service?.provides)) return record.contributes.service.provides;
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Whether this declaration says the extension provides the browser role. */
|
|
81
|
+
export const extensionDeclaresBrowser = (declaration) => {
|
|
82
|
+
const provides = readDeclaredProvides(declaration);
|
|
83
|
+
return Boolean(provides?.some((role) => role === BROWSER_PROVIDER_ROLE));
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {string} endpoint
|
|
88
|
+
* @returns {string | null} Absolute loopback URL, or null when it must not be called.
|
|
89
|
+
*/
|
|
90
|
+
export const resolveBrowserProviderEndpoint = (endpoint) => {
|
|
91
|
+
if (typeof endpoint !== 'string' || endpoint.length === 0) return null;
|
|
92
|
+
let parsed;
|
|
93
|
+
try {
|
|
94
|
+
parsed = new URL(endpoint);
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
if (parsed.username || parsed.password) return null;
|
|
99
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return null;
|
|
100
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, '');
|
|
101
|
+
if (host !== '127.0.0.1' && host !== 'localhost' && host !== '::1') return null;
|
|
102
|
+
if (parsed.pathname === '/' || parsed.pathname === '') {
|
|
103
|
+
parsed.pathname = BROWSER_PROVIDER_PATH;
|
|
104
|
+
}
|
|
105
|
+
return parsed.toString();
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const readBrowserProviderId = (value) => (
|
|
109
|
+
typeof value === 'string' && PROVIDER_ID_PATTERN.test(value) ? value : null
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const readProviderName = (value, id) => {
|
|
113
|
+
if (typeof value !== 'string') return id;
|
|
114
|
+
const trimmed = value.trim();
|
|
115
|
+
if (!trimmed || trimmed.length > PROVIDER_NAME_MAX) return null;
|
|
116
|
+
return trimmed;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Public row for settings and the rail. Transport details stay on the host.
|
|
121
|
+
* A declaration the host cannot reach is not listed: offering it would look
|
|
122
|
+
* like a connected browser that cannot answer.
|
|
123
|
+
*
|
|
124
|
+
* @param {unknown} record
|
|
125
|
+
* @returns {{ id: string, name: string, surface: false } | null}
|
|
126
|
+
*/
|
|
127
|
+
export const toPublicBrowserProvider = (record) => {
|
|
128
|
+
if (!record || typeof record !== 'object') return null;
|
|
129
|
+
const row = /** @type {{ id?: unknown, name?: unknown, enabled?: unknown, answer?: unknown, endpoint?: unknown }} */ (record);
|
|
130
|
+
if (row.enabled === false) return null;
|
|
131
|
+
if (!extensionDeclaresBrowser(record)) return null;
|
|
132
|
+
const id = readBrowserProviderId(row.id);
|
|
133
|
+
if (!id) return null;
|
|
134
|
+
const name = readProviderName(row.name, id);
|
|
135
|
+
if (!name) return null;
|
|
136
|
+
const endpoint = typeof row.endpoint === 'string' ? resolveBrowserProviderEndpoint(row.endpoint) : null;
|
|
137
|
+
if (typeof row.answer !== 'function' && !endpoint) return null;
|
|
138
|
+
// This host does not push frames. A true surface would make the rail open a
|
|
139
|
+
// picture that does not exist.
|
|
140
|
+
return { id, name, surface: false };
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @param {unknown} value
|
|
145
|
+
* @returns {{ directory: string | null, sessionId: string | null }}
|
|
146
|
+
*/
|
|
147
|
+
export const readBrowserProviderContext = (value) => {
|
|
148
|
+
const record = value && typeof value === 'object'
|
|
149
|
+
? /** @type {{ directory?: unknown, sessionId?: unknown }} */ (value)
|
|
150
|
+
: {};
|
|
151
|
+
const directory = record.directory;
|
|
152
|
+
const sessionId = record.sessionId;
|
|
153
|
+
return {
|
|
154
|
+
directory: typeof directory === 'string' && directory.length > 0 ? directory : null,
|
|
155
|
+
sessionId: typeof sessionId === 'string' && sessionId.length > 0 ? sessionId : null,
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Extension-side read of the body the host posts. `null` is not a browser action.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} body
|
|
163
|
+
*/
|
|
164
|
+
export const readBrowserProviderRequest = (body) => {
|
|
165
|
+
let wire;
|
|
166
|
+
try {
|
|
167
|
+
wire = JSON.parse(body);
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
if (!wire || typeof wire !== 'object' || Array.isArray(wire)) return null;
|
|
172
|
+
const { requestId, action, parameters, context } = wire;
|
|
173
|
+
if (typeof requestId !== 'string' || requestId.length === 0) return null;
|
|
174
|
+
if (!isBrowserControlAction(action)) return null;
|
|
175
|
+
if (!parameters || typeof parameters !== 'object' || Array.isArray(parameters)) return null;
|
|
176
|
+
return {
|
|
177
|
+
requestId,
|
|
178
|
+
action,
|
|
179
|
+
parameters,
|
|
180
|
+
context: readBrowserProviderContext(context),
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Host-side read of an extension answer. `null` is not a browser result.
|
|
186
|
+
*
|
|
187
|
+
* @param {unknown} value
|
|
188
|
+
* @returns {{ ok: true, data: Record<string, unknown> } | { ok: false, error: string } | null}
|
|
189
|
+
*/
|
|
190
|
+
export const parseBrowserProviderResult = (value) => {
|
|
191
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
192
|
+
const row = /** @type {{ ok?: unknown, data?: unknown, error?: unknown }} */ (value);
|
|
193
|
+
if (row.ok === true) {
|
|
194
|
+
if (!row.data || typeof row.data !== 'object' || Array.isArray(row.data)) return null;
|
|
195
|
+
return { ok: true, data: /** @type {Record<string, unknown>} */ (row.data) };
|
|
196
|
+
}
|
|
197
|
+
if (row.ok === false) {
|
|
198
|
+
if (typeof row.error !== 'string' || row.error.length === 0 || row.error.length > 2_000) return null;
|
|
199
|
+
return { ok: false, error: row.error };
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const invalidAction = (message) => new BrowserProviderError(message, { code: 'INVALID_ACTION', status: 400 });
|
|
205
|
+
|
|
206
|
+
const readOptionalString = (value) => (typeof value === 'string' && value.trim().length > 0 ? value.trim() : null);
|
|
207
|
+
|
|
208
|
+
const readViewport = (value, required) => {
|
|
209
|
+
const viewport = readOptionalString(value);
|
|
210
|
+
if (!viewport) {
|
|
211
|
+
if (required) throw invalidAction('viewport is required for browser.resize. Nothing was sent.');
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
if (!BROWSER_VIEWPORT_MODES.includes(viewport)) {
|
|
215
|
+
throw invalidAction('viewport must be mobile, tablet, desktop, or fill. Nothing was sent.');
|
|
216
|
+
}
|
|
217
|
+
return viewport;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Validates an agent action before it is sent. The object returned is the
|
|
222
|
+
* only parameters the extension sees.
|
|
223
|
+
*
|
|
224
|
+
* @param {unknown} action
|
|
225
|
+
* @param {unknown} parameters
|
|
226
|
+
* @returns {{ action: string, parameters: Record<string, unknown> }}
|
|
227
|
+
*/
|
|
228
|
+
export const normalizeBrowserAction = (action, parameters) => {
|
|
229
|
+
if (!isBrowserControlAction(action)) {
|
|
230
|
+
throw invalidAction('Unknown browser action. Nothing was sent.');
|
|
231
|
+
}
|
|
232
|
+
const input = parameters == null ? {} : parameters;
|
|
233
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
234
|
+
throw invalidAction('parameters must be an object. Nothing was sent.');
|
|
235
|
+
}
|
|
236
|
+
const source = /** @type {Record<string, unknown>} */ (input);
|
|
237
|
+
/** @type {Record<string, unknown>} */
|
|
238
|
+
const next = {};
|
|
239
|
+
|
|
240
|
+
if (action === 'browser.resize') {
|
|
241
|
+
next.viewport = readViewport(source.viewport, true);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (action === 'browser.capture') {
|
|
245
|
+
const label = readOptionalString(source.label);
|
|
246
|
+
if (label) next.label = label;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (action === 'browser.open') {
|
|
250
|
+
const viewport = readViewport(source.viewport, false);
|
|
251
|
+
if (viewport) next.viewport = viewport;
|
|
252
|
+
const url = readOptionalString(source.url);
|
|
253
|
+
if (!url) throw invalidAction('url is required for browser.open. Nothing was sent.');
|
|
254
|
+
let parsed;
|
|
255
|
+
try {
|
|
256
|
+
parsed = new URL(url);
|
|
257
|
+
} catch {
|
|
258
|
+
throw invalidAction('url must be an absolute http(s) URL. Nothing was sent.');
|
|
259
|
+
}
|
|
260
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
261
|
+
throw invalidAction('url must use http or https. Nothing was sent.');
|
|
262
|
+
}
|
|
263
|
+
next.url = parsed.toString();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (action === 'browser.click') {
|
|
267
|
+
const selector = readOptionalString(source.selector);
|
|
268
|
+
const text = readOptionalString(source.text);
|
|
269
|
+
if (!selector && !text) {
|
|
270
|
+
throw invalidAction('browser.click requires selector or text. Nothing was sent.');
|
|
271
|
+
}
|
|
272
|
+
if (selector) next.selector = selector;
|
|
273
|
+
if (text) next.text = text;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (action === 'browser.snapshot') {
|
|
277
|
+
const selector = readOptionalString(source.selector);
|
|
278
|
+
if (selector) next.selector = selector;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (action === 'browser.inspect') {
|
|
282
|
+
const selector = readOptionalString(source.selector);
|
|
283
|
+
if (!selector) throw invalidAction('selector is required for browser.inspect. Nothing was sent.');
|
|
284
|
+
next.selector = selector;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (action === 'browser.type') {
|
|
288
|
+
const selector = readOptionalString(source.selector);
|
|
289
|
+
if (!selector) throw invalidAction('selector is required for browser.type. Nothing was sent.');
|
|
290
|
+
if (typeof source.value !== 'string') {
|
|
291
|
+
throw invalidAction('value is required for browser.type. Nothing was sent.');
|
|
292
|
+
}
|
|
293
|
+
next.selector = selector;
|
|
294
|
+
next.value = source.value;
|
|
295
|
+
next.submit = source.submit === true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (action === 'browser.scroll') {
|
|
299
|
+
const selector = readOptionalString(source.selector);
|
|
300
|
+
const direction = readOptionalString(source.direction);
|
|
301
|
+
if (!selector && !direction) {
|
|
302
|
+
throw invalidAction('browser.scroll requires direction or selector. Nothing was sent.');
|
|
303
|
+
}
|
|
304
|
+
if (direction && !BROWSER_SCROLL_DIRECTIONS.includes(direction)) {
|
|
305
|
+
throw invalidAction('direction must be up, down, top, or bottom. Nothing was sent.');
|
|
306
|
+
}
|
|
307
|
+
if (selector) next.selector = selector;
|
|
308
|
+
if (direction) next.direction = direction;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return { action, parameters: next };
|
|
312
|
+
};
|