@xopcai/xopc 0.0.266 → 0.0.267
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-ext/dist/assets/{auth-CknJ00ae.js → auth-D_IL6naL.js} +603 -203
- package/dist/browser-ext/dist/assets/sidepanel-CEPaVLjr.css +155 -0
- package/dist/browser-ext/dist/background.js +698 -169
- package/dist/browser-ext/dist/sidepanel.html +3 -3
- package/dist/browser-ext/dist/sidepanel.js +148 -391
- package/dist/browser-ext/manifest.json +3 -6
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Qrp5ALCF.js → agent-avatar-dicebear-ZrSAMchm.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-DwinST48.js → agent-defaults-settings-panel-DiNFN8qS.js} +1 -1
- package/dist/gateway/static/root/assets/{agents-Dlr0YyNt.js → agents-Bu6Hf59K.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-bLNvz93c.js → app-management-settings-panel-D-L6yjS7.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-D5qgIepF.js → appearance-settings-BW5eb53j.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-BQVlXhIb.js → apps-page-CiQIdz3N.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-BEA7VAA4.js → archive-plugin-DwXvu1p9.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-B1RyF3uu.js → attachment-load-DqU2BYBp.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-BfH0FdKa.js → automations-page-C7qoj9Sd.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-workspace-C37AlD3E.js → automations-workspace-CIARTLHO.js} +1 -1
- package/dist/gateway/static/root/assets/{binary-plugins-EU884ZXi.js → binary-plugins-CfplDak2.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-Bjr3naHb.js → block-editor-CzLs38ru.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-CDrYrNGy.js → browser-automations-page-_SKi8s9b.js} +1 -1
- package/dist/gateway/static/root/assets/browser-settings-page-DTdx66sX.js +8 -0
- package/dist/gateway/static/root/assets/{capabilities-settings-panel-slt1o04n.js → capabilities-settings-panel-Ck9U0Olx.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-settings-oHTTX6Jy.js → channels-settings-I_vDpbnl.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-DEYQdv6n.js → chat-terminal-dock-snvAu3hJ.js} +1 -1
- package/dist/gateway/static/root/assets/{connectors-page-olrbxHIT.js → connectors-page-BmKTnzo1.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-expression-DFrbDWpP.js → cron-expression-BhGZSbT6.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-CYriYKpH.js → date-picker-Cep5f9b-.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-XY3qUErs.js → dependency-picker-BzSEBQku.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-Dsc__ynA.js → desktop-pet-BwX_6Gah.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-EpuX3C08.js → desktop-pet-settings-WrbVsU2b.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-B_NeLV4K.js → extension-debug-page-BdXSwtAZ.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-D3JC6mLB.js → extension-page-I95ukbRb.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CXTQt1lp.js → extension-settings-page-8zUisBay.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-yeYm_UPE.js → gateway-settings-DGj5b9Yv.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-coh1LFsg.js → heartbeat-settings-BGQxU-Ug.js} +1 -1
- package/dist/gateway/static/root/assets/{home-page-CAgoBZqp.js → home-page-BZPpK0A3.js} +1 -1
- package/dist/gateway/static/root/assets/{index-Cn__9Un2.js → index-D2OtD9Wa.js} +7 -7
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Cu1bI3Bg.js → keyboard-shortcuts-settings-BUe_om99.js} +1 -1
- package/dist/gateway/static/root/assets/{local-app-workbench-page-C9s2lFaI.js → local-app-workbench-page-CbQnffS9.js} +1 -1
- package/dist/gateway/static/root/assets/{local-apps-page-CJ03Umjr.js → local-apps-page-BZaLzfms.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-CY_DQKJ2.js → logs-page-WWCHqW_x.js} +1 -1
- package/dist/gateway/static/root/assets/{management-settings-YPEemeMo.js → management-settings-Duf8w-Bl.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-BjT6jlrA.js → media-plugins-2-QfzHmg.js} +1 -1
- package/dist/gateway/static/root/assets/{mobile-pairing-wizard-A8eLtV6v.js → mobile-pairing-wizard-DRaVIAt1.js} +1 -1
- package/dist/gateway/static/root/assets/{note-detail-panel-DHT2QksJ.js → note-detail-panel-BT-fWGxX.js} +3 -3
- package/dist/gateway/static/root/assets/{notes-page-BRFQiivM.js → notes-page-C4705PzN.js} +1 -1
- package/dist/gateway/static/root/assets/{page-tabs-DNUwulnl.js → page-tabs-DaZ7QpNO.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-B33KsKkJ.js → preview-open-alternatives-CWEuqCmv.js} +1 -1
- package/dist/gateway/static/root/assets/{project-detail-page-CItEFVt9.js → project-detail-page-BZegJ6an.js} +1 -1
- package/dist/gateway/static/root/assets/{projects-page-Bmy-c_lL.js → projects-page-B7hUHeLI.js} +1 -1
- package/dist/gateway/static/root/assets/{remote-access-hub-H6B1cA2V.js → remote-access-hub-BE9hZpdh.js} +1 -1
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BsCovC39.js → runtime-tools-settings-panel-CslkSM-m.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-NsPb_Vam.js → schema-form-B2gvVrBq.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-CyYLSXk_.js → sessions-page-DmAAPMo1.js} +1 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-CvBocGDA.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-BzPIPe1-.js → settings-form-section-CmlnuSO3.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-4idpMhsw.js → settings-page-DWWgyelT.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-D2ds_TTD.js → setup-status-panel-CJrN3m1o.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-Djo3VTaC.js → share-preview-page-C12oyIKk.js} +1 -1
- package/dist/gateway/static/root/assets/{shares-settings-fhRzNfRp.js → shares-settings-D4BtSiLq.js} +1 -1
- package/dist/gateway/static/root/assets/{skill-reload-api-Ct8tUju_.js → skill-reload-api-CzKGn056.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-riM7FytZ.js → skills-page-yhW7LLqk.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-B3G8LBb-.js → skills-page.utils-FaTlq8t-.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-CW91jjnB.js → system-settings-panel-CMrkoJLN.js} +1 -1
- package/dist/gateway/static/root/assets/{task-detail-page-BBXKvl0k.js → task-detail-page-BUaO62cy.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-DILER0Yb.js → tunnel-api-C2tAZaJz.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-GxnP8vLO.js → use-autosave-BsDRt4QI.js} +1 -1
- package/dist/gateway/static/root/assets/{user-model-page-I1H4XRfA.js → user-model-page-Dy8h1SSJ.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-CI3sXyl4.js → voice-api-key-field-CS5heYMP.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-DZC9Exn3.js → work-discovery-overlay-BeSz5Th5.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CaBaQER2.js → workflow-run-setup-panel-BebW0CDK.js} +1 -1
- package/dist/gateway/static/root/assets/{workflows-page-C-_Dxihw.js → workflows-page-CU8jFgPF.js} +1 -1
- package/dist/gateway/static/root/index.html +1 -1
- package/dist/package.js +1 -1
- package/dist/packages/browser-control-contract/src/index.js +30 -1
- package/dist/src/agent/external-tools/endpoint-provider.js +3 -1
- package/dist/src/agent/tools/factory.js +1 -1
- package/dist/src/browser/automations/runtime-service.d.ts +2 -0
- package/dist/src/browser/automations/runtime-service.js +1 -1
- package/dist/src/browser/drivers/create-driver.d.ts +2 -1
- package/dist/src/browser/drivers/create-driver.js +4 -6
- package/dist/src/browser/drivers/extension-driver.d.ts +7 -2
- package/dist/src/browser/enrollment.d.ts +8 -0
- package/dist/src/browser/enrollment.js +25 -0
- package/dist/src/browser/extension-identity.d.ts +2 -0
- package/dist/src/browser/extension-identity.js +5 -0
- package/dist/src/browser/native-messaging-host.d.ts +6 -3
- package/dist/src/browser/native-messaging-host.js +23 -5
- package/dist/src/browser/providers/browser-ext-install.d.ts +4 -1
- package/dist/src/browser/providers/browser-ext-install.js +46 -7
- package/dist/src/browser/providers/extension.js +1 -1
- package/dist/src/browser/providers/realtime-extension.d.ts +29 -0
- package/dist/src/browser/providers/realtime-extension.js +83 -0
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/endpoint-tools/policy.js +10 -3
- package/dist/src/gateway/hono/app.js +6 -1
- package/dist/src/gateway/hono/routes/browser.js +20 -30
- package/dist/src/gateway/security/origin-check.js +5 -0
- package/dist/src/gateway/service/config-coordinator.d.ts +0 -2
- package/dist/src/gateway/service/config-coordinator.js +0 -2
- package/dist/src/gateway/service.d.ts +0 -11
- package/dist/src/gateway/service.js +7 -85
- package/dist/src/storage/sqlite/device-pairing-approval.js +24 -3
- package/dist/src/storage/sqlite/device-pairing-repository.d.ts +10 -1
- package/dist/src/storage/sqlite/device-pairing-repository.js +6 -5
- package/dist/src/storage/sqlite/migrations/157_browser_endpoint_principals.sql +50 -0
- package/dist/src/storage/sqlite/migrations/158_browser_native_enrollment.sql +4 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/package.json +3 -3
- package/dist/browser-ext/dist/assets/sidepanel-C69P3v9u.css +0 -113
- package/dist/gateway/static/root/assets/browser-settings-page-DOmUkj2k.js +0 -8
- package/dist/gateway/static/root/assets/settings-advanced-gate-3T4rPk4A.js +0 -1
|
@@ -6,7 +6,7 @@ import { BrowserAutomationService } from "./service.js";
|
|
|
6
6
|
function createRuntimeBrowserAutomationService(deps) {
|
|
7
7
|
const runtime = new BrowserRuntime({
|
|
8
8
|
getConfig: () => deps.getConfig().browser,
|
|
9
|
-
createDriver: () => createBrowserDriver(deps.getConfig().browser),
|
|
9
|
+
createDriver: () => createBrowserDriver(deps.getConfig().browser, deps.endpointTools),
|
|
10
10
|
allowedUploadRoots: [process.cwd()],
|
|
11
11
|
emit: deps.emit
|
|
12
12
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { Config } from '../../config/schema.js';
|
|
2
|
+
import type { EndpointToolRuntime } from '../../endpoint-tools/index.js';
|
|
2
3
|
import type { BrowserDriver } from './browser-driver.js';
|
|
3
|
-
export declare function createBrowserDriver(config: Config['browser']): Promise<BrowserDriver>;
|
|
4
|
+
export declare function createBrowserDriver(config: Config['browser'], endpointTools: EndpointToolRuntime): Promise<BrowserDriver>;
|
|
@@ -3,22 +3,20 @@ import { init_logger } from "../../utils/logger.js";
|
|
|
3
3
|
import { loadPlaywrightCoreModule } from "../providers/playwright-doctor.js";
|
|
4
4
|
import { BrowserbaseProvider } from "../providers/browserbase.js";
|
|
5
5
|
import { BrowserUseProvider } from "../providers/browser-use.js";
|
|
6
|
-
import {
|
|
6
|
+
import { RealtimeExtensionBrowserProvider } from "../providers/realtime-extension.js";
|
|
7
7
|
import { ExtensionDriver } from "./extension-driver.js";
|
|
8
8
|
import { PlaywrightDriver } from "./playwright-driver.js";
|
|
9
9
|
//#region src/browser/drivers/create-driver.ts
|
|
10
10
|
init_logger();
|
|
11
11
|
const log = createLogger("BrowserDriverFactory");
|
|
12
|
-
async function createBrowserDriver(config) {
|
|
12
|
+
async function createBrowserDriver(config, endpointTools) {
|
|
13
13
|
const limits = config.limits;
|
|
14
14
|
if (config.driver.kind === "extension") {
|
|
15
|
-
const
|
|
16
|
-
host: "127.0.0.1",
|
|
17
|
-
port: 19820,
|
|
15
|
+
const provider = new RealtimeExtensionBrowserProvider(endpointTools, {
|
|
18
16
|
connectionTimeout: limits.actionTimeoutMs,
|
|
19
17
|
commandTimeout: limits.actionTimeoutMs
|
|
20
18
|
});
|
|
21
|
-
return new ExtensionDriver(provider, limits.actionTimeoutMs, config.observation.visualFallback,
|
|
19
|
+
return new ExtensionDriver(provider, limits.actionTimeoutMs, config.observation.visualFallback, () => provider.shutdown());
|
|
22
20
|
}
|
|
23
21
|
const driver = config.driver;
|
|
24
22
|
return new PlaywrightDriver({
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { BrowserControlResult, BrowserNavigateInput, BrowserObservation, BrowserObserveInput, BrowserTabsInput } from '@xopcai/browser-control-contract';
|
|
2
|
-
import type { ExtensionBrowserProvider } from '../providers/extension.js';
|
|
3
2
|
import type { BrowserDriver, BrowserPrimitiveInput } from './browser-driver.js';
|
|
3
|
+
type ExtensionProvider = {
|
|
4
|
+
start(): Promise<void>;
|
|
5
|
+
waitForConnection(timeoutMs?: number): Promise<void>;
|
|
6
|
+
send(input: import('@xopcai/browser-control-contract').BrowserActionInput, timeoutMs?: number, visualFallback?: boolean): Promise<import('@xopcai/browser-control-contract').BrowserWireResult>;
|
|
7
|
+
};
|
|
4
8
|
export declare class ExtensionDriver implements BrowserDriver {
|
|
5
9
|
private readonly provider;
|
|
6
10
|
private readonly timeoutMs;
|
|
7
11
|
private readonly visualFallback;
|
|
8
12
|
private readonly release;
|
|
9
13
|
readonly kind: "extension";
|
|
10
|
-
constructor(provider:
|
|
14
|
+
constructor(provider: ExtensionProvider, timeoutMs: number, visualFallback: boolean, release: () => Promise<void>);
|
|
11
15
|
connect(): Promise<void>;
|
|
12
16
|
disconnect(): Promise<void>;
|
|
13
17
|
createSession(_sessionId: string): Promise<void>;
|
|
@@ -18,3 +22,4 @@ export declare class ExtensionDriver implements BrowserDriver {
|
|
|
18
22
|
tabs(sessionId: string, input: BrowserTabsInput): Promise<BrowserControlResult>;
|
|
19
23
|
private send;
|
|
20
24
|
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type BrowserEnrollmentPublicKeyJwk = {
|
|
2
|
+
kty: 'EC';
|
|
3
|
+
crv: 'P-256';
|
|
4
|
+
x: string;
|
|
5
|
+
y: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function parseBrowserEnrollmentPublicKeyJwk(value: unknown): BrowserEnrollmentPublicKeyJwk;
|
|
8
|
+
export declare function browserEnrollmentPublicKeyThumbprint(value: unknown): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
//#region src/browser/enrollment.ts
|
|
3
|
+
function parseBrowserEnrollmentPublicKeyJwk(value) {
|
|
4
|
+
if (!value || typeof value !== "object") throw new Error("Browser enrollment public key is missing");
|
|
5
|
+
const key = value;
|
|
6
|
+
if (key.kty !== "EC" || key.crv !== "P-256" || typeof key.x !== "string" || !key.x || typeof key.y !== "string" || !key.y) throw new Error("Browser enrollment public key is invalid");
|
|
7
|
+
return {
|
|
8
|
+
kty: "EC",
|
|
9
|
+
crv: "P-256",
|
|
10
|
+
x: key.x,
|
|
11
|
+
y: key.y
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function browserEnrollmentPublicKeyThumbprint(value) {
|
|
15
|
+
const key = parseBrowserEnrollmentPublicKeyJwk(value);
|
|
16
|
+
const canonical = JSON.stringify({
|
|
17
|
+
crv: key.crv,
|
|
18
|
+
kty: key.kty,
|
|
19
|
+
x: key.x,
|
|
20
|
+
y: key.y
|
|
21
|
+
});
|
|
22
|
+
return crypto.createHash("sha256").update(canonical).digest("base64url");
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { browserEnrollmentPublicKeyThumbprint, parseBrowserEnrollmentPublicKeyJwk };
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
export
|
|
2
|
-
export declare const BROWSER_EXTENSION_ID = "gopbfhaojnnhiheiikblejnpgmfmkmgd";
|
|
1
|
+
export { BROWSER_EXTENSION_ID, BROWSER_NATIVE_HOST_NAME } from './extension-identity.js';
|
|
3
2
|
export type BrowserNativeBootstrap = {
|
|
4
3
|
type: 'bootstrap';
|
|
4
|
+
extensionId: string;
|
|
5
|
+
publicKeyJwk: unknown;
|
|
6
|
+
publicKeyThumbprint: string;
|
|
7
|
+
nonce: string;
|
|
5
8
|
};
|
|
6
9
|
export type BrowserNativeBootstrapResult = {
|
|
7
10
|
ok: true;
|
|
@@ -11,5 +14,5 @@ export type BrowserNativeBootstrapResult = {
|
|
|
11
14
|
};
|
|
12
15
|
export declare function encodeNativeMessage(value: unknown): Buffer;
|
|
13
16
|
export declare function decodeNativeMessage(buffer: Buffer): unknown;
|
|
14
|
-
export declare function createBrowserNativeBootstrap(configPath: string): BrowserNativeBootstrapResult;
|
|
17
|
+
export declare function createBrowserNativeBootstrap(configPath: string, request: Omit<BrowserNativeBootstrap, 'type'>): BrowserNativeBootstrapResult;
|
|
15
18
|
export declare function runBrowserNativeMessagingHost(configPath: string): Promise<void>;
|
|
@@ -2,11 +2,11 @@ import { closeXopcDatabase, openXopcDatabase } from "../storage/sqlite/connectio
|
|
|
2
2
|
import { getGatewayIdentityPublicKeyRaw, getOrCreateGatewayIdentity } from "../storage/sqlite/gateway-identity-repository.js";
|
|
3
3
|
import { createDevicePairingSetup } from "../storage/sqlite/device-pairing-repository.js";
|
|
4
4
|
import { init_loader, loadConfig } from "../config/loader.js";
|
|
5
|
+
import { browserEnrollmentPublicKeyThumbprint } from "./enrollment.js";
|
|
6
|
+
import { BROWSER_EXTENSION_ID, BROWSER_NATIVE_HOST_NAME } from "./extension-identity.js";
|
|
5
7
|
import os from "node:os";
|
|
6
8
|
//#region src/browser/native-messaging-host.ts
|
|
7
9
|
init_loader();
|
|
8
|
-
const BROWSER_NATIVE_HOST_NAME = "ai.xopc.browser";
|
|
9
|
-
const BROWSER_EXTENSION_ID = "gopbfhaojnnhiheiikblejnpgmfmkmgd";
|
|
10
10
|
function gatewayLoopbackUrl(port) {
|
|
11
11
|
return `http://127.0.0.1:${port}`;
|
|
12
12
|
}
|
|
@@ -22,7 +22,11 @@ function decodeNativeMessage(buffer) {
|
|
|
22
22
|
if (length === 0 || length > 1024 * 1024 || buffer.length !== length + 4) throw new Error("Native message length is invalid");
|
|
23
23
|
return JSON.parse(buffer.subarray(4).toString("utf8"));
|
|
24
24
|
}
|
|
25
|
-
function createBrowserNativeBootstrap(configPath) {
|
|
25
|
+
function createBrowserNativeBootstrap(configPath, request) {
|
|
26
|
+
if (request.extensionId !== "gopbfhaojnnhiheiikblejnpgmfmkmgd") throw new Error("Browser extension identity is not trusted");
|
|
27
|
+
if (!/^[A-Za-z0-9_-]{24,160}$/.test(request.nonce)) throw new Error("Browser enrollment nonce is invalid");
|
|
28
|
+
const publicKeyThumbprint = browserEnrollmentPublicKeyThumbprint(request.publicKeyJwk);
|
|
29
|
+
if (request.publicKeyThumbprint !== publicKeyThumbprint) throw new Error("Browser enrollment key does not match");
|
|
26
30
|
const gatewayUrl = gatewayLoopbackUrl(loadConfig(configPath).gateway.port ?? 18790);
|
|
27
31
|
openXopcDatabase();
|
|
28
32
|
try {
|
|
@@ -31,7 +35,15 @@ function createBrowserNativeBootstrap(configPath) {
|
|
|
31
35
|
kind: "local-browser",
|
|
32
36
|
url: gatewayUrl
|
|
33
37
|
};
|
|
34
|
-
const setup = createDevicePairingSetup([route], Date.now(), 3
|
|
38
|
+
const setup = createDevicePairingSetup([route], Date.now(), 3, {
|
|
39
|
+
ttlMs: 6e4,
|
|
40
|
+
enrollment: {
|
|
41
|
+
issuer: "browser-native-host",
|
|
42
|
+
extensionId: request.extensionId,
|
|
43
|
+
publicKeyThumbprint,
|
|
44
|
+
nonce: request.nonce
|
|
45
|
+
}
|
|
46
|
+
});
|
|
35
47
|
const identity = getOrCreateGatewayIdentity();
|
|
36
48
|
return {
|
|
37
49
|
ok: true,
|
|
@@ -93,7 +105,13 @@ async function runBrowserNativeMessagingHost(configPath) {
|
|
|
93
105
|
try {
|
|
94
106
|
const request = decodeNativeMessage(await readOneNativeMessage(process.stdin));
|
|
95
107
|
if (!request || typeof request !== "object" || request.type !== "bootstrap") throw new Error("Unsupported native messaging request");
|
|
96
|
-
|
|
108
|
+
const bootstrap = request;
|
|
109
|
+
process.stdout.write(encodeNativeMessage(createBrowserNativeBootstrap(configPath, {
|
|
110
|
+
extensionId: bootstrap.extensionId,
|
|
111
|
+
publicKeyJwk: bootstrap.publicKeyJwk,
|
|
112
|
+
publicKeyThumbprint: bootstrap.publicKeyThumbprint,
|
|
113
|
+
nonce: bootstrap.nonce
|
|
114
|
+
})));
|
|
97
115
|
} catch (error) {
|
|
98
116
|
process.stdout.write(encodeNativeMessage({
|
|
99
117
|
ok: false,
|
|
@@ -26,6 +26,7 @@ export interface BrowserExtDoctor {
|
|
|
26
26
|
needsChromeReload?: boolean;
|
|
27
27
|
bundledFrom?: BrowserExtBundledFrom;
|
|
28
28
|
runtimeExtensionVersion?: string;
|
|
29
|
+
nativeHost: BrowserNativeHostInstallResult;
|
|
29
30
|
}
|
|
30
31
|
export interface EnsureBrowserExtResult {
|
|
31
32
|
extensionDir: string;
|
|
@@ -65,17 +66,19 @@ export declare function ensureBrowserExtensionArtifacts(opts?: {
|
|
|
65
66
|
cacheDir?: string;
|
|
66
67
|
}): Promise<EnsureBrowserExtResult>;
|
|
67
68
|
export declare function browserNativeManifestDirectories(platform?: NodeJS.Platform, home?: string): string[];
|
|
69
|
+
export declare function browserNativeHostDoctor(platform?: NodeJS.Platform, home?: string): BrowserNativeHostInstallResult;
|
|
68
70
|
/** Install the local-only bootstrap host used to discover and pair with a Gateway. */
|
|
69
71
|
export declare function installBrowserNativeMessagingHost(opts?: {
|
|
70
72
|
cacheDir?: string;
|
|
71
73
|
cliPath?: string;
|
|
72
74
|
nodePath?: string;
|
|
75
|
+
tsxCliPath?: string;
|
|
73
76
|
platform?: NodeJS.Platform;
|
|
74
77
|
home?: string;
|
|
75
78
|
configPath?: string;
|
|
76
79
|
stateDir?: string;
|
|
77
80
|
}): Promise<BrowserNativeHostInstallResult>;
|
|
78
|
-
/** Gateway startup hook:
|
|
81
|
+
/** Gateway startup hook: install/repair extension artifacts and its local enrollment host. */
|
|
79
82
|
export declare function ensureBrowserExtensionOnStartup(config: Config | undefined): Promise<void>;
|
|
80
83
|
export declare function readInstalledExtensionDir(cacheDir?: string): Promise<string | null>;
|
|
81
84
|
export type BrowserExtensionOpenAction = 'chrome' | 'folder' | 'both';
|
|
@@ -5,10 +5,11 @@ import { init_logger } from "../../utils/logger.js";
|
|
|
5
5
|
import { init_write_file_atomic, writeTextAtomic } from "../../infra/write-file-atomic.js";
|
|
6
6
|
import { init_paths, resolveBinDir, resolveConfigPath } from "../../config/paths.js";
|
|
7
7
|
import { assertCacheDir } from "../cache-dir-policy.js";
|
|
8
|
-
import { BROWSER_EXTENSION_ID, BROWSER_NATIVE_HOST_NAME } from "../
|
|
8
|
+
import { BROWSER_EXTENSION_ID, BROWSER_NATIVE_HOST_NAME } from "../extension-identity.js";
|
|
9
|
+
import "../native-messaging-host.js";
|
|
9
10
|
import { resolvePackageRoot } from "../../infra/update-check.js";
|
|
10
11
|
import { dirname, join } from "node:path";
|
|
11
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
12
13
|
import { fileURLToPath } from "node:url";
|
|
13
14
|
import { spawn } from "node:child_process";
|
|
14
15
|
import { createHash } from "node:crypto";
|
|
@@ -240,7 +241,8 @@ async function browserExtDoctor(opts) {
|
|
|
240
241
|
needsRefresh,
|
|
241
242
|
needsChromeReload,
|
|
242
243
|
bundledFrom: bundled?.bundledFrom,
|
|
243
|
-
runtimeExtensionVersion: runtimeVer
|
|
244
|
+
runtimeExtensionVersion: runtimeVer,
|
|
245
|
+
nativeHost: browserNativeHostDoctor()
|
|
244
246
|
};
|
|
245
247
|
}
|
|
246
248
|
async function ensureBrowserExtensionArtifacts(opts) {
|
|
@@ -302,33 +304,68 @@ function browserNativeManifestDirectories(platform = process.platform, home = pr
|
|
|
302
304
|
if (!home) return [];
|
|
303
305
|
if (platform === "darwin") return [
|
|
304
306
|
join(home, "Library/Application Support/Google/Chrome/NativeMessagingHosts"),
|
|
307
|
+
join(home, "Library/Application Support/Google/ChromeForTesting/NativeMessagingHosts"),
|
|
305
308
|
join(home, "Library/Application Support/Chromium/NativeMessagingHosts"),
|
|
306
309
|
join(home, "Library/Application Support/Microsoft Edge/NativeMessagingHosts"),
|
|
307
310
|
join(home, "Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts")
|
|
308
311
|
];
|
|
309
312
|
if (platform === "linux") return [
|
|
310
313
|
join(home, ".config/google-chrome/NativeMessagingHosts"),
|
|
314
|
+
join(home, ".config/google-chrome-for-testing/NativeMessagingHosts"),
|
|
311
315
|
join(home, ".config/chromium/NativeMessagingHosts"),
|
|
312
316
|
join(home, ".config/microsoft-edge/NativeMessagingHosts"),
|
|
313
317
|
join(home, ".config/BraveSoftware/Brave-Browser/NativeMessagingHosts")
|
|
314
318
|
];
|
|
315
319
|
return [];
|
|
316
320
|
}
|
|
321
|
+
function browserNativeHostDoctor(platform = process.platform, home = process.env.HOME || process.env.USERPROFILE || "") {
|
|
322
|
+
const directories = browserNativeManifestDirectories(platform, home);
|
|
323
|
+
if (directories.length === 0) return {
|
|
324
|
+
installed: false,
|
|
325
|
+
manifestPaths: [],
|
|
326
|
+
reason: "Automatic local Gateway discovery is supported on macOS and Linux"
|
|
327
|
+
};
|
|
328
|
+
const manifestPaths = directories.map((directory) => join(directory, `${BROWSER_NATIVE_HOST_NAME}.json`)).filter((manifestPath) => {
|
|
329
|
+
try {
|
|
330
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
331
|
+
return manifest.name === "ai.xopc.browser" && typeof manifest.path === "string" && existsSync(manifest.path) && Array.isArray(manifest.allowed_origins) && manifest.allowed_origins.includes(`chrome-extension://gopbfhaojnnhiheiikblejnpgmfmkmgd/`);
|
|
332
|
+
} catch {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
return {
|
|
337
|
+
installed: manifestPaths.length > 0,
|
|
338
|
+
manifestPaths,
|
|
339
|
+
...manifestPaths.length === 0 ? { reason: "Browser native enrollment host is not installed" } : {}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
317
342
|
/** Install the local-only bootstrap host used to discover and pair with a Gateway. */
|
|
318
343
|
async function installBrowserNativeMessagingHost(opts) {
|
|
319
344
|
const directories = browserNativeManifestDirectories(opts?.platform ?? process.platform, opts?.home);
|
|
320
345
|
if (directories.length === 0) return {
|
|
321
346
|
installed: false,
|
|
322
347
|
manifestPaths: [],
|
|
323
|
-
reason: "
|
|
348
|
+
reason: "Automatic local Gateway discovery is supported on macOS and Linux"
|
|
324
349
|
};
|
|
325
350
|
const cliPath = opts?.cliPath ?? process.argv[1];
|
|
326
351
|
if (!cliPath) throw new Error("Unable to resolve the xopc CLI entry point");
|
|
352
|
+
const executablePath = opts?.nodePath ?? process.execPath;
|
|
353
|
+
let executableArgs = [
|
|
354
|
+
cliPath,
|
|
355
|
+
"browser",
|
|
356
|
+
"extension",
|
|
357
|
+
"native-host"
|
|
358
|
+
];
|
|
359
|
+
if (cliPath.endsWith(".ts")) {
|
|
360
|
+
const tsxPath = opts?.tsxCliPath ?? join(process.cwd(), "node_modules", "tsx", "dist", "cli.mjs");
|
|
361
|
+
if (!existsSync(tsxPath)) throw new Error("Unable to install the browser native host in development: the tsx CLI entry was not found");
|
|
362
|
+
executableArgs = [realpathSync(tsxPath), ...executableArgs];
|
|
363
|
+
}
|
|
327
364
|
const hostPath = join((opts?.cacheDir?.trim() ? assertCacheDir(opts.cacheDir) : resolveBinDir()) || resolveBinDir(), "browser-native-host");
|
|
328
365
|
mkdirSync(dirname(hostPath), { recursive: true });
|
|
329
366
|
const stateDir = opts?.stateDir ?? resolveStateDir();
|
|
330
367
|
const configPath = opts?.configPath ?? resolveConfigPath();
|
|
331
|
-
await writeTextAtomic(hostPath, `#!/bin/sh\nexec env XOPC_STATE_DIR=${shellQuote(stateDir)} XOPC_CONFIG_PATH=${shellQuote(configPath)} XOPC_LOG_CONSOLE=false ${shellQuote(
|
|
368
|
+
await writeTextAtomic(hostPath, `#!/bin/sh\nexec env ELECTRON_RUN_AS_NODE=1 XOPC_STATE_DIR=${shellQuote(stateDir)} XOPC_CONFIG_PATH=${shellQuote(configPath)} XOPC_LOG_CONSOLE=false ${shellQuote(executablePath)} ${executableArgs.map(shellQuote).join(" ")}\n`);
|
|
332
369
|
chmodSync(hostPath, 493);
|
|
333
370
|
const manifest = JSON.stringify({
|
|
334
371
|
name: BROWSER_NATIVE_HOST_NAME,
|
|
@@ -349,10 +386,12 @@ async function installBrowserNativeMessagingHost(opts) {
|
|
|
349
386
|
manifestPaths
|
|
350
387
|
};
|
|
351
388
|
}
|
|
352
|
-
/** Gateway startup hook:
|
|
389
|
+
/** Gateway startup hook: install/repair extension artifacts and its local enrollment host. */
|
|
353
390
|
async function ensureBrowserExtensionOnStartup(config) {
|
|
354
391
|
if (config?.browser.enabled === false || config?.browser.driver.kind !== "extension") return;
|
|
355
392
|
await ensureBrowserExtensionArtifacts();
|
|
393
|
+
const nativeHost = await installBrowserNativeMessagingHost();
|
|
394
|
+
if (!nativeHost.installed) log.warn({ reason: nativeHost.reason }, "Browser native enrollment host was not installed");
|
|
356
395
|
}
|
|
357
396
|
async function readInstalledExtensionDir(cacheDir) {
|
|
358
397
|
const resolved = (cacheDir?.trim() ? assertCacheDir(cacheDir) : resolveBinDir()) || resolveBinDir();
|
|
@@ -432,4 +471,4 @@ async function openBrowserExtensionInstallUi(opts) {
|
|
|
432
471
|
};
|
|
433
472
|
}
|
|
434
473
|
//#endregion
|
|
435
|
-
export { BROWSER_EXT_REQUIRED_FILES, browserExtContentHash, browserExtDoctor, browserNativeManifestDirectories, computeNeedsRefresh, ensureBrowserExtensionArtifacts, ensureBrowserExtensionOnStartup, installBrowserNativeMessagingHost, openBrowserExtensionInstallUi, readInstalledExtensionDir, resolveBundledBrowserExtDir, resolveInstalledExtensionPath, resolveWindowsExtensionManager, validateBrowserExtLayout };
|
|
474
|
+
export { BROWSER_EXT_REQUIRED_FILES, browserExtContentHash, browserExtDoctor, browserNativeHostDoctor, browserNativeManifestDirectories, computeNeedsRefresh, ensureBrowserExtensionArtifacts, ensureBrowserExtensionOnStartup, installBrowserNativeMessagingHost, openBrowserExtensionInstallUi, readInstalledExtensionDir, resolveBundledBrowserExtDir, resolveInstalledExtensionPath, resolveWindowsExtensionManager, validateBrowserExtLayout };
|
|
@@ -2,8 +2,8 @@ import { createLogger } from "../../utils/logger/index.js";
|
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
3
|
import { deleteBrowserTabBindingsByPrincipal, getBrowserTabBindingById } from "../../storage/sqlite/browser-tab-binding-repository.js";
|
|
4
4
|
import { getDevice } from "../../storage/sqlite/device-access-repository.js";
|
|
5
|
-
import { isChromeExtensionOrigin } from "../../gateway/security/origin-check.js";
|
|
6
5
|
import { browserWireAuthenticationPayload } from "../../../packages/browser-control-contract/src/index.js";
|
|
6
|
+
import { isChromeExtensionOrigin } from "../../gateway/security/origin-check.js";
|
|
7
7
|
import crypto from "node:crypto";
|
|
8
8
|
//#region src/browser/providers/extension.ts
|
|
9
9
|
/**
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type BrowserActionInput, type BrowserWireResult } from '@xopcai/browser-control-contract';
|
|
2
|
+
import type { EndpointToolRuntime } from '../../endpoint-tools/index.js';
|
|
3
|
+
export interface RealtimeExtensionProviderConfig {
|
|
4
|
+
connectionTimeout?: number;
|
|
5
|
+
commandTimeout?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class RealtimeExtensionBrowserProvider {
|
|
8
|
+
private readonly runtime;
|
|
9
|
+
readonly name = "extension";
|
|
10
|
+
private readonly connectionTimeout;
|
|
11
|
+
private readonly commandTimeout;
|
|
12
|
+
constructor(runtime: EndpointToolRuntime, config?: RealtimeExtensionProviderConfig);
|
|
13
|
+
start(): Promise<void>;
|
|
14
|
+
waitForConnection(timeoutMs?: number): Promise<void>;
|
|
15
|
+
send(input: BrowserActionInput, timeoutMs?: number, visualFallback?: boolean): Promise<BrowserWireResult>;
|
|
16
|
+
isConnected(): boolean;
|
|
17
|
+
getConnectionStatus(): {
|
|
18
|
+
socketConnected: boolean;
|
|
19
|
+
connected: boolean;
|
|
20
|
+
protocolVersion: number;
|
|
21
|
+
expectedProtocolVersion: number;
|
|
22
|
+
extensionVersion: string;
|
|
23
|
+
principalId: string;
|
|
24
|
+
transport: "gateway-realtime";
|
|
25
|
+
};
|
|
26
|
+
shutdown(): Promise<void>;
|
|
27
|
+
private endpointForInput;
|
|
28
|
+
private currentEndpoint;
|
|
29
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { getBrowserTabBindingById } from "../../storage/sqlite/browser-tab-binding-repository.js";
|
|
2
|
+
import { BROWSER_CONTROL_ENDPOINT_TOOL_NAME } from "../../../packages/browser-control-contract/src/index.js";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
//#region src/browser/providers/realtime-extension.ts
|
|
5
|
+
var RealtimeExtensionBrowserProvider = class {
|
|
6
|
+
name = "extension";
|
|
7
|
+
connectionTimeout;
|
|
8
|
+
commandTimeout;
|
|
9
|
+
constructor(runtime, config = {}) {
|
|
10
|
+
this.runtime = runtime;
|
|
11
|
+
this.connectionTimeout = config.connectionTimeout ?? 3e4;
|
|
12
|
+
this.commandTimeout = config.commandTimeout ?? 3e4;
|
|
13
|
+
}
|
|
14
|
+
async start() {}
|
|
15
|
+
async waitForConnection(timeoutMs = this.connectionTimeout) {
|
|
16
|
+
const deadline = Date.now() + timeoutMs;
|
|
17
|
+
while (!this.currentEndpoint()) {
|
|
18
|
+
if (Date.now() >= deadline) throw new Error(`Extension connection timeout after ${timeoutMs}ms. Open the xopc Chrome extension and connect it to this Gateway.`);
|
|
19
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async send(input, timeoutMs = this.commandTimeout, visualFallback = true) {
|
|
23
|
+
const endpoint = this.endpointForInput(input);
|
|
24
|
+
if (!endpoint) throw new Error("Extension not connected to this Gateway. Open the xopc Chrome extension and connect it first.");
|
|
25
|
+
const tool = this.runtime.registry.getTool(endpoint.endpointId, BROWSER_CONTROL_ENDPOINT_TOOL_NAME);
|
|
26
|
+
if (!tool) throw new Error("Connected browser extension does not support authenticated Realtime browser control. Reload or update the extension.");
|
|
27
|
+
const id = crypto.randomUUID();
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
30
|
+
try {
|
|
31
|
+
const value = (await this.runtime.invocations.invoke({
|
|
32
|
+
endpointId: endpoint.endpointId,
|
|
33
|
+
toolCallId: `browser-control:${id}`,
|
|
34
|
+
toolName: tool.descriptor.name,
|
|
35
|
+
arguments: {
|
|
36
|
+
input,
|
|
37
|
+
timeoutMs,
|
|
38
|
+
visualFallback
|
|
39
|
+
},
|
|
40
|
+
descriptorRevision: tool.revision,
|
|
41
|
+
signal: controller.signal
|
|
42
|
+
})).content.find((item) => item.type === "json");
|
|
43
|
+
if (!value || !value.value || typeof value.value !== "object") throw new Error("Browser extension returned an invalid Realtime result");
|
|
44
|
+
return {
|
|
45
|
+
id,
|
|
46
|
+
connectionId: endpoint.endpointId,
|
|
47
|
+
result: value.value
|
|
48
|
+
};
|
|
49
|
+
} finally {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
isConnected() {
|
|
54
|
+
return Boolean(this.currentEndpoint());
|
|
55
|
+
}
|
|
56
|
+
getConnectionStatus() {
|
|
57
|
+
const endpoint = this.currentEndpoint();
|
|
58
|
+
return {
|
|
59
|
+
socketConnected: Boolean(endpoint),
|
|
60
|
+
connected: Boolean(endpoint),
|
|
61
|
+
protocolVersion: endpoint ? 4 : null,
|
|
62
|
+
expectedProtocolVersion: 4,
|
|
63
|
+
extensionVersion: endpoint?.appVersion ?? null,
|
|
64
|
+
principalId: endpoint?.principalId ?? null,
|
|
65
|
+
transport: "gateway-realtime"
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async shutdown() {}
|
|
69
|
+
endpointForInput(input) {
|
|
70
|
+
if (input.target?.kind === "attached_tab") {
|
|
71
|
+
const binding = getBrowserTabBindingById(input.target.bindingId);
|
|
72
|
+
const endpoint = binding ? this.runtime.registry.get(binding.endpointId) : void 0;
|
|
73
|
+
if (!binding || !endpoint || endpoint.principalId !== binding.principalId) return void 0;
|
|
74
|
+
return endpoint;
|
|
75
|
+
}
|
|
76
|
+
return this.currentEndpoint();
|
|
77
|
+
}
|
|
78
|
+
currentEndpoint() {
|
|
79
|
+
return this.runtime.registry.list().filter((endpoint) => endpoint.kind === "browser" && endpoint.tools.some((tool) => tool.descriptor.name === "browser.control")).sort((left, right) => right.lastHeartbeatAt - left.lastHeartbeatAt)[0];
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
//#endregion
|
|
83
|
+
export { RealtimeExtensionBrowserProvider };
|
|
@@ -52,7 +52,7 @@ async function checkDatabaseSchema(ctx) {
|
|
|
52
52
|
id: "database-schema",
|
|
53
53
|
label: "Database schema",
|
|
54
54
|
status: "pass",
|
|
55
|
-
message: `SQLite schema v${status.dbVersion} (current release
|
|
55
|
+
message: `SQLite schema v${status.dbVersion} (current release v158).`,
|
|
56
56
|
hints: []
|
|
57
57
|
};
|
|
58
58
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ENDPOINT_CONTACT_LIST_OUTPUT_SCHEMA, ENDPOINT_CONTACT_OUTPUT_SCHEMA, ENDPOINT_FILE_OUTPUT_SCHEMA, ENDPOINT_TEXT_OUTPUT_SCHEMA, canonicalJson } from "../../packages/endpoint-tools-protocol/src/index.js";
|
|
2
|
+
import { BROWSER_CONTROL_ENDPOINT_INPUT_SCHEMA, BROWSER_CONTROL_ENDPOINT_OUTPUT_SCHEMA } from "../../packages/browser-control-contract/src/index.js";
|
|
2
3
|
import Ajv2020 from "ajv/dist/2020.js";
|
|
3
4
|
//#region src/endpoint-tools/policy.ts
|
|
4
5
|
var EndpointToolPolicyError = class extends Error {};
|
|
5
|
-
function contract(policyId, outputSchema, resultKinds, permissions, legacyPolicyIds = []) {
|
|
6
|
+
function contract(policyId, outputSchema, resultKinds, permissions, legacyPolicyIds = [], inputSchema) {
|
|
6
7
|
return {
|
|
7
8
|
policyId,
|
|
8
9
|
legacyPolicyIds,
|
|
10
|
+
inputSchema,
|
|
9
11
|
outputSchema,
|
|
10
12
|
resultKinds,
|
|
11
13
|
permissions
|
|
@@ -31,7 +33,8 @@ const POLICY_BY_TOOL = {
|
|
|
31
33
|
"mobile.notification.show": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["notifications"]),
|
|
32
34
|
"mobile.device.get_info": contract("public.background-read", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], []),
|
|
33
35
|
"mobile.clipboard.write": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["clipboard-write"]),
|
|
34
|
-
"mobile.app.open_url": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["open-external-url"])
|
|
36
|
+
"mobile.app.open_url": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["open-external-url"]),
|
|
37
|
+
"browser.control": contract("browser.control", BROWSER_CONTROL_ENDPOINT_OUTPUT_SCHEMA, ["json"], ["browser-control"], [], BROWSER_CONTROL_ENDPOINT_INPUT_SCHEMA)
|
|
35
38
|
};
|
|
36
39
|
var EndpointToolPolicy = class {
|
|
37
40
|
ajv = new Ajv2020({
|
|
@@ -43,7 +46,7 @@ var EndpointToolPolicy = class {
|
|
|
43
46
|
const contract = POLICY_BY_TOOL[descriptor.name];
|
|
44
47
|
if (!contract) throw new EndpointToolPolicyError(`Tool ${descriptor.name} has no trusted server policy`);
|
|
45
48
|
if (descriptor.policyId !== contract.policyId && !contract.legacyPolicyIds.includes(descriptor.policyId)) throw new EndpointToolPolicyError(`Tool ${descriptor.name} does not match its trusted server policy`);
|
|
46
|
-
if (canonicalJson(descriptor.outputSchema) !== canonicalJson(contract.outputSchema) || canonicalJson(descriptor.resultKinds) !== canonicalJson(contract.resultKinds) || canonicalJson([...descriptor.requiredPermissions].sort()) !== canonicalJson([...contract.permissions].sort())) throw new EndpointToolPolicyError(`Tool ${descriptor.name} does not match its trusted server contract`);
|
|
49
|
+
if (contract.inputSchema && canonicalJson(descriptor.inputSchema) !== canonicalJson(contract.inputSchema) || canonicalJson(descriptor.outputSchema) !== canonicalJson(contract.outputSchema) || canonicalJson(descriptor.resultKinds) !== canonicalJson(contract.resultKinds) || canonicalJson([...descriptor.requiredPermissions].sort()) !== canonicalJson([...contract.permissions].sort())) throw new EndpointToolPolicyError(`Tool ${descriptor.name} does not match its trusted server contract`);
|
|
47
50
|
this.validatePolicyFields(descriptor);
|
|
48
51
|
try {
|
|
49
52
|
this.ajv.compile(descriptor.outputSchema);
|
|
@@ -79,6 +82,10 @@ var EndpointToolPolicy = class {
|
|
|
79
82
|
if (descriptor.effect === "read") throw new EndpointToolPolicyError(`Mutating tool ${descriptor.name} cannot declare a read effect`);
|
|
80
83
|
if (descriptor.confirmation !== "always" || !descriptor.requiresForeground || descriptor.sensitivity !== "personal" || descriptor.requiredPermissions.length === 0) throw new EndpointToolPolicyError(`Mutating tool ${descriptor.name} violates its trusted policy`);
|
|
81
84
|
return;
|
|
85
|
+
case "browser.control":
|
|
86
|
+
this.assertFields(descriptor, "write", "never", false, "personal");
|
|
87
|
+
if (descriptor.requiredPermissions.length !== 1 || descriptor.requiredPermissions[0] !== "browser-control" || descriptor.timeoutMs !== 12e4 || descriptor.maxConcurrency !== 4 || descriptor.supportsCancellation !== true || descriptor.idempotent !== false) throw new EndpointToolPolicyError(`Browser tool ${descriptor.name} violates its trusted policy`);
|
|
88
|
+
return;
|
|
82
89
|
default: throw new EndpointToolPolicyError(`Unknown endpoint policy: ${descriptor.policyId}`);
|
|
83
90
|
}
|
|
84
91
|
}
|
|
@@ -5,6 +5,7 @@ import { resolveAllowedBrowserOrigins, resolveGatewayServiceListenPort } from ".
|
|
|
5
5
|
import { resolveGatewayEffectiveHost } from "../../config/gateway-bind.js";
|
|
6
6
|
import { loadTunnelState } from "../../tunnel/tunnel-state.js";
|
|
7
7
|
import { resolveReverseProxyPublicUrl } from "../public-url.js";
|
|
8
|
+
import { BROWSER_EXTENSION_ID } from "../../browser/extension-identity.js";
|
|
8
9
|
import { maxSessionInputRequestBodyBytes } from "../chat-limits.js";
|
|
9
10
|
import { isLoopbackIpAddress, isTrustedProxyAddress } from "../client-ip.js";
|
|
10
11
|
import { buildGatewayConsoleCspHeader } from "../security/csp.js";
|
|
@@ -65,7 +66,11 @@ function createHonoApp(config) {
|
|
|
65
66
|
return [];
|
|
66
67
|
}
|
|
67
68
|
};
|
|
68
|
-
const resolveAllBrowserOrigins = () => [
|
|
69
|
+
const resolveAllBrowserOrigins = () => [
|
|
70
|
+
...resolveBrowserOrigins(),
|
|
71
|
+
`chrome-extension://${BROWSER_EXTENSION_ID}`,
|
|
72
|
+
...resolvePairedBrowserExtensionOrigins()
|
|
73
|
+
];
|
|
69
74
|
/**
|
|
70
75
|
* TCP source for the in-flight request, normalized for trusted-proxy checks.
|
|
71
76
|
* Returns undefined when the runtime doesn't expose conninfo (tests, mocks).
|