@xopcai/xopc 0.0.273 → 0.0.275
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/_locales/en/messages.json +215 -0
- package/dist/browser-ext/_locales/zh_CN/messages.json +215 -0
- package/dist/browser-ext/dist/assets/{auth-DkwOUI6_.js → auth-ClQTK565.js} +291 -115
- package/dist/browser-ext/dist/assets/{sidepanel-C86O7q5Y.css → sidepanel-CeGqObOa.css} +41 -20
- package/dist/browser-ext/dist/background.js +36 -4
- package/dist/browser-ext/dist/sidepanel.html +2 -2
- package/dist/browser-ext/dist/sidepanel.js +457 -310
- package/dist/browser-ext/manifest.json +6 -4
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-cF7IX2RP.js → activity-store-SJZ5fS17.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BrgxX8nG.js → agent-avatar-dicebear-Dx6x7MXq.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-DvhuRCLA.js → agent-defaults-settings-panel-BuxTpjkf.js} +1 -1
- package/dist/gateway/static/root/assets/agents-_f9HEOol.js +1 -0
- package/dist/gateway/static/root/assets/{api-CYkdUpkx.js → api-CnJAVHBu.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-DfoNzWIa.js → app-management-settings-panel-BYKWXcVl.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-czbLdCqj.js → appearance-settings-BmhSJc4X.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-DYTnK9lL.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-D14P13mX.js → archive-plugin-BY6ncIeE.js} +1 -1
- package/dist/gateway/static/root/assets/attachment-load-B6cw8KtO.js +1 -0
- package/dist/gateway/static/root/assets/{attachment-preview-renderer-BDTD8YUe.js → attachment-preview-renderer-CfEDFYPE.js} +1 -1
- package/dist/gateway/static/root/assets/attachment-process-heavy-C5CqLCsX.js +5 -0
- package/dist/gateway/static/root/assets/{automations-page-DM7ZEewI.js → automations-page-D1WwsSf3.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-CjF56MI_.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-CxNg6InF.js → binary-plugins-CC-wYm0K.js} +2 -2
- package/dist/gateway/static/root/assets/{block-editor-D22VcYnC.js → block-editor-D8rh9KNe.js} +3 -3
- package/dist/gateway/static/root/assets/{browser-automation-inputs-6NWTLEpd.js → browser-automation-inputs-DGCqIPXT.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-BrqySdbW.js → browser-automations-page-CMrffyct.js} +2 -2
- package/dist/gateway/static/root/assets/browser-settings-page-1onvP1Se.js +8 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-C9ZSJxjr.js +8 -0
- package/dist/gateway/static/root/assets/{channels-settings-yHsY174W.js → channels-settings-CvLTnI99.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-status-swr-BUZimNmp.js → channels-status-swr-BTT2sIGg.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-CZPBNpMI.js → chat-terminal-dock-F_dUdZ8l.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-D7NHV8B2.js +2 -0
- package/dist/gateway/static/root/assets/{date-picker-Da_0QyLe.js → date-picker-MHuajwQ-.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-D_djqzy7.js → dependency-picker-BcMJJI9X.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-Celmbw-T.js → desktop-pet-DlNm_7H0.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-CqNNgF45.js → desktop-pet-settings-BI1JQMnE.js} +1 -1
- package/dist/gateway/static/root/assets/device-pairing-wizard-Cp5jO5B_.js +1 -0
- package/dist/gateway/static/root/assets/excel-worksheet-utils-DpdB-DjU.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-ku3kCjvH.js → extension-debug-page-DZr0GkkA.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-C7FpHwL4.js → extension-page-QsHoQ6VX.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-OMj2tU2_.js → extension-settings-page-I2YX4Tzb.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-6eb1vKE1.js → gateway-config-swr-cd8owslz.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-GwfQu8rN.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-DUFFCU8H.js → gateway-startup-retry-CVTa5HSS.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-CSFzOACM.js → heartbeat-settings-D-hVYXzL.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-SEW_0k8Q.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-C1cHRBRp.js → image-generation-api-DtMRG1oF.js} +1 -1
- package/dist/gateway/static/root/assets/{index-C6OWYFtQ.js → index-DfZZLrWp.js} +35 -35
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BuvD3iv8.js → keyboard-shortcuts-settings-D_I-hiG2.js} +1 -1
- package/dist/gateway/static/root/assets/{local-app-workbench-page-CaC4-THD.js → local-app-workbench-page-BcY7wIHs.js} +2 -2
- package/dist/gateway/static/root/assets/{local-apps-page-DdJ8zwBp.js → local-apps-page-DniZuXJJ.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-BBSCIl4I.js → locale-store-BkJ7g35d.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-BaR2he0K.js → logs-page-DIiOMrJ_.js} +2 -2
- package/dist/gateway/static/root/assets/management-settings-BPw41YX6.js +1 -0
- package/dist/gateway/static/root/assets/{markdown-view-c5vtnYe8.js → markdown-view-B2IR4E2r.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-D_a6GDY6.js → media-plugins-BoP3LSQz.js} +1 -1
- package/dist/gateway/static/root/assets/{messages-DjuBdDCn.js → messages-BGc-OJWg.js} +2 -2
- package/dist/gateway/static/root/assets/note-detail-panel-BI2xr54R.js +5 -0
- package/dist/gateway/static/root/assets/notes-page-CM7Ns8i1.js +8 -0
- package/dist/gateway/static/root/assets/{page-tabs-C7Le9bSI.js → page-tabs-B21WfV92.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-B2pOxJBK.js → preview-open-alternatives-BZoDpPqV.js} +1 -1
- package/dist/gateway/static/root/assets/{proactive-card-BlQ1vCtV.js → proactive-card-Bw9CbzUX.js} +1 -1
- package/dist/gateway/static/root/assets/{proactive-page-CghKTfYa.js → proactive-page-Czw_CEDB.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-DpjiHgwu.js → product-open-page-DsQcgZkI.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-jOECabbf.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-Ck9j5q0x.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-CyQ4k1Ux.js → registry-api-CTq8EECj.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-2xcpaLTC.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-Bl4ArLHf.js → runtime-tools-settings-panel-Bz5gIRjy.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-KQFmfWd4.js → schema-form-CqM0ntNm.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-CE9MK60k.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-D0grhQan.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-Dbnls9ty.js → settings-form-section-LqcDYJ2m.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-CDKirYRG.js → settings-page-BpvBmheO.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-CYuqNzWH.js +1 -0
- package/dist/gateway/static/root/assets/share-preview-page-F3qhAryJ.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-B4wSoQM3.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-C3LD7fkR.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-Bd0_ED_p.js → skills-page-D9fI548d.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-rA-XT-YG.js → skills-page.utils-DZNmyYqU.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-CSkMrHLz.js → system-settings-panel-CMsmFOhV.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-DRwTJ8E0.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-DWpIEWen.js → theme-store-mV6Cm2vj.js} +1 -1
- package/dist/gateway/static/root/assets/tunnel-api-DGaqXwql.js +1 -0
- package/dist/gateway/static/root/assets/{use-autosave-DkQAIAuB.js → use-autosave-bXCl0Y0j.js} +1 -1
- package/dist/gateway/static/root/assets/user-model-page-B3RjDSri.js +2 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-BrqcH0P-.js → voice-api-key-field-Cx9rl7mP.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-BJ95Z3pT.js → voice-config-api-D3DdcH4H.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-vfTWKm13.js → work-discovery-overlay-ywJpaqKp.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CHdr_ng9.js → workflow-run-setup-panel-DyGukxOL.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-CifIqO2B.js +6 -0
- package/dist/gateway/static/root/index.html +9 -9
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/device-pairing.js +41 -2
- package/dist/src/agent/child-agent-factory.d.ts +3 -0
- package/dist/src/agent/child-agent-factory.js +2 -1
- package/dist/src/agent/external-tools/endpoint-provider.js +1 -1
- package/dist/src/agent/service.js +20 -16
- package/dist/src/agent/tools/factory.d.ts +1 -0
- package/dist/src/agent/tools/factory.js +21 -4
- package/dist/src/agent/workflow/workflow-child-tools.js +2 -0
- package/dist/src/browser/native-messaging-host.d.ts +1 -1
- package/dist/src/browser/native-messaging-host.js +3 -2
- package/dist/src/browser/providers/browser-ext-install.d.ts +1 -1
- package/dist/src/browser/providers/browser-ext-install.js +8 -3
- package/dist/src/browser/providers/realtime-extension.d.ts +4 -1
- package/dist/src/browser/providers/realtime-extension.js +26 -10
- package/dist/src/browser/readiness.d.ts +4 -1
- package/dist/src/browser/readiness.js +11 -5
- package/dist/src/cli/commands/onboard.js +1 -1
- package/dist/src/gateway/gateway-workflow-host.types.d.ts +2 -0
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/app.js +5 -2
- package/dist/src/gateway/hono/routes/agent-stream.js +1 -1
- package/dist/src/gateway/hono/routes/browser.js +24 -11
- package/dist/src/gateway/hono/routes/device-pairing-approval.d.ts +9 -2
- package/dist/src/gateway/hono/routes/device-pairing-approval.js +50 -10
- package/dist/src/gateway/hono/routes/devices.d.ts +2 -1
- package/dist/src/gateway/hono/routes/devices.js +33 -16
- package/dist/src/gateway/hono/routes/endpoint-tools.js +9 -0
- package/dist/src/gateway/hono/routes/models.js +6 -0
- package/dist/src/gateway/hono/routes/session-input-handler.d.ts +1 -1
- package/dist/src/gateway/hono/routes/session-input-handler.js +1 -1
- package/dist/src/gateway/hono/routes/side-chats.js +1 -1
- package/dist/src/gateway/hono/routes/tunnel.js +34 -2
- package/dist/src/gateway/hono/routes/voice.js +1 -1
- package/dist/src/gateway/rate-limit/buckets.js +2 -1
- package/dist/src/gateway/service/run-gateway-agent.js +1 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/providers/model-catalog-persistence.js +4 -0
- package/dist/src/providers/model-catalog-types.d.ts +1 -0
- package/dist/src/providers/xopc-cloud-model-source.js +12 -1
- package/dist/src/storage/sqlite/device-pairing-approval.js +3 -1
- package/dist/src/storage/sqlite/endpoint-session-binding-repository.d.ts +1 -0
- package/dist/src/storage/sqlite/endpoint-session-binding-repository.js +9 -1
- package/dist/src/storage/sqlite/index.d.ts +1 -1
- package/dist/src/storage/sqlite/index.js +2 -2
- package/dist/src/tui/tui.js +1 -1
- package/dist/src/tunnel/xopc-cloud-registration.d.ts +5 -0
- package/dist/src/tunnel/xopc-cloud-registration.js +17 -11
- package/dist/src/workflows/service/workflow-run-service.js +4 -1
- package/package.json +2 -2
- package/dist/gateway/static/root/assets/agents-C0By2FIo.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-BSJXKWcE.js +0 -1
- package/dist/gateway/static/root/assets/attachment-load-Dok_B1Gv.js +0 -2
- package/dist/gateway/static/root/assets/attachment-process-heavy-B2_kr5EA.js +0 -11
- package/dist/gateway/static/root/assets/automations-workspace--1TsZf3O.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-DOHgQer_.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-Bpse_5K-.js +0 -8
- package/dist/gateway/static/root/assets/connectors-page-B_wLKGnH.js +0 -2
- package/dist/gateway/static/root/assets/device-pairing-wizard-B5_w9qlm.js +0 -1
- package/dist/gateway/static/root/assets/excel-worksheet-utils-EyvwNT4X.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-CH-6vkgN.js +0 -1
- package/dist/gateway/static/root/assets/home-page-BJebAKB-.js +0 -1
- package/dist/gateway/static/root/assets/management-settings-r-iYWZFY.js +0 -1
- package/dist/gateway/static/root/assets/note-detail-panel-BV6nbgac.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-BmLKoWwi.js +0 -8
- package/dist/gateway/static/root/assets/project-detail-page-CV8Lh296.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-COweOaJP.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-ChPPGVU5.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-DgmHibOK.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DV7kMco2.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-SQmtWoOs.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-DwgLjS1m.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-Cs5bUrYw.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-DyKrjq7F.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-CEQU-L_G.js +0 -1
- package/dist/gateway/static/root/assets/tunnel-api-COJcGnEi.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-BQAU55lf.js +0 -2
- package/dist/gateway/static/root/assets/workflows-page-hwRJD7vR.js +0 -6
|
@@ -24,6 +24,7 @@ import { runXopcEmbeddedTurn } from "./embedded/run-turn.js";
|
|
|
24
24
|
import { resolveResponseLanguageForSession } from "./prompt/response-language.js";
|
|
25
25
|
import { normalizeSkillInstallTarget } from "./skills/install-target.js";
|
|
26
26
|
import { AgentManager } from "./agent-manager.js";
|
|
27
|
+
import { MAX_WEBCHAT_ATTACHMENT_FILE_BYTES } from "../gateway/chat-limits.js";
|
|
27
28
|
import { maybeRefineSessionTitleWithLlm, maybeSetProvisionalSessionTitle } from "../session/session-title.js";
|
|
28
29
|
import { resolveCompactionPolicy } from "./memory/compaction-policy.js";
|
|
29
30
|
import { SessionStore } from "../session/store.js";
|
|
@@ -748,22 +749,25 @@ var AgentService = class {
|
|
|
748
749
|
* Persist inbound file attachments to the global media store.
|
|
749
750
|
*/
|
|
750
751
|
async prepareInboundAttachments(_sessionKey, attachments) {
|
|
751
|
-
return persistInboundAttachments(attachments, {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
buffer
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
752
|
+
return persistInboundAttachments(attachments, {
|
|
753
|
+
maxBytes: MAX_WEBCHAT_ATTACHMENT_FILE_BYTES,
|
|
754
|
+
resolveUri: async (uri) => {
|
|
755
|
+
const target = parseNoteAttachmentTarget(uri);
|
|
756
|
+
if (!target) return null;
|
|
757
|
+
const notesService = this.notesServiceInstance;
|
|
758
|
+
if (!notesService) return null;
|
|
759
|
+
const located = await notesService.getAttachmentPath(target.noteId, target.attachmentId);
|
|
760
|
+
if (!located) return null;
|
|
761
|
+
const buffer = await readFile(located.filePath);
|
|
762
|
+
return {
|
|
763
|
+
buffer,
|
|
764
|
+
path: located.filePath,
|
|
765
|
+
mimeType: located.mimeType,
|
|
766
|
+
name: located.fileName,
|
|
767
|
+
size: buffer.byteLength
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
});
|
|
767
771
|
}
|
|
768
772
|
endDirectRequestContext() {
|
|
769
773
|
this.contextMiddleware.onResponse();
|
|
@@ -105,6 +105,7 @@ export declare class AgentToolsFactory {
|
|
|
105
105
|
constructor(deps: ToolFactoryDeps);
|
|
106
106
|
private prepareRuntimeEnv;
|
|
107
107
|
private browserReadinessKey;
|
|
108
|
+
private hasBrowserEndpoint;
|
|
108
109
|
private checkBrowserReadinessCached;
|
|
109
110
|
/** Invalidate the readiness cache (config hot-reload, settings-page save, etc.). */
|
|
110
111
|
invalidateBrowserReadinessCache(): void;
|
|
@@ -8,6 +8,7 @@ import { getSessionTaskPlan, setSessionTaskPlan } from "../../storage/sqlite/ses
|
|
|
8
8
|
import "../../storage/sqlite/index.js";
|
|
9
9
|
import { init_agent_scope, resolveDefaultAgentId } from "../agent-scope.js";
|
|
10
10
|
import { resolveEffectiveAgentConfigForSession } from "../../config/agent-profile.js";
|
|
11
|
+
import "../../../packages/browser-control-contract/src/index.js";
|
|
11
12
|
import { getEmbeddedExecutionRunId, getEmbeddedExecutionSession } from "../embedded/execution-context.js";
|
|
12
13
|
import { wrapToolsWithProtection } from "./executor.js";
|
|
13
14
|
import { EXTERNAL_TOOL_NAMES } from "../external-tools/gateway-tools.js";
|
|
@@ -98,7 +99,13 @@ var AgentToolsFactory = class AgentToolsFactory {
|
|
|
98
99
|
})).env;
|
|
99
100
|
};
|
|
100
101
|
browserReadinessKey() {
|
|
101
|
-
return JSON.stringify(
|
|
102
|
+
return JSON.stringify({
|
|
103
|
+
driver: this.deps.getConfig?.()?.browser.driver ?? null,
|
|
104
|
+
extensionConnected: this.hasBrowserEndpoint()
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
hasBrowserEndpoint() {
|
|
108
|
+
return this.deps.endpointTools?.registry.list().some((endpoint) => endpoint.kind === "browser" && endpoint.tools.some((tool) => tool.descriptor.name === "browser.control")) ?? false;
|
|
102
109
|
}
|
|
103
110
|
async checkBrowserReadinessCached() {
|
|
104
111
|
const key = this.browserReadinessKey();
|
|
@@ -106,7 +113,10 @@ var AgentToolsFactory = class AgentToolsFactory {
|
|
|
106
113
|
const cached = this.browserReadinessCache;
|
|
107
114
|
if (cached && cached.key === key && cached.expiresAt > now && cached.inflight === void 0) return cached.result ?? null;
|
|
108
115
|
if (cached && cached.key === key && cached.inflight) return cached.inflight;
|
|
109
|
-
const inflight = checkBrowserReadiness(this.deps.getConfig?.()
|
|
116
|
+
const inflight = checkBrowserReadiness(this.deps.getConfig?.(), {
|
|
117
|
+
extensionConnected: this.hasBrowserEndpoint(),
|
|
118
|
+
checkExtensionInstall: false
|
|
119
|
+
});
|
|
110
120
|
this.browserReadinessCache = {
|
|
111
121
|
key,
|
|
112
122
|
expiresAt: now + 3e4,
|
|
@@ -146,9 +156,14 @@ var AgentToolsFactory = class AgentToolsFactory {
|
|
|
146
156
|
emit: this.deps.emitBrowserEvent,
|
|
147
157
|
resolveTarget: (sessionKey) => {
|
|
148
158
|
const binding = getBrowserTabBinding(sessionKey);
|
|
149
|
-
|
|
159
|
+
if (binding) return {
|
|
150
160
|
kind: "attached_tab",
|
|
151
161
|
bindingId: binding.id
|
|
162
|
+
};
|
|
163
|
+
const endpointBinding = this.deps.endpointTools?.bindings.get(sessionKey);
|
|
164
|
+
return endpointBinding ? {
|
|
165
|
+
kind: "endpoint",
|
|
166
|
+
endpointId: endpointBinding.endpointId
|
|
152
167
|
} : void 0;
|
|
153
168
|
}
|
|
154
169
|
});
|
|
@@ -401,7 +416,7 @@ var AgentToolsFactory = class AgentToolsFactory {
|
|
|
401
416
|
})] : [],
|
|
402
417
|
...browserEnabled ? [createBrowserUseTool({
|
|
403
418
|
getRuntime: () => this.ensureBrowserRuntime(),
|
|
404
|
-
getTaskId: () => this.deps.getCurrentContext()?.sessionKey ?? "default",
|
|
419
|
+
getTaskId: () => options?.sessionKey ?? this.deps.getCurrentContext()?.sessionKey ?? "default",
|
|
405
420
|
getReadiness: () => this.checkBrowserReadinessCached()
|
|
406
421
|
})] : [],
|
|
407
422
|
...primary ? [createWorkflowTool({
|
|
@@ -428,11 +443,13 @@ var AgentToolsFactory = class AgentToolsFactory {
|
|
|
428
443
|
getCurrentContext: () => null,
|
|
429
444
|
getConfig: childOpts.getConfig,
|
|
430
445
|
getPrimaryModel: () => childOpts.model,
|
|
446
|
+
endpointTools: childOpts.endpointTools ?? this.deps.endpointTools,
|
|
431
447
|
toolExecutorConfig: childOpts.toolExecutorConfig
|
|
432
448
|
}).createAllTools({
|
|
433
449
|
workspace: childOpts.workspace,
|
|
434
450
|
getPrimaryModel: () => childOpts.model,
|
|
435
451
|
agentId: options?.agentId ?? childOpts.agentId,
|
|
452
|
+
sessionKey: childOpts.browserSessionKey,
|
|
436
453
|
disabledTools: new Set([
|
|
437
454
|
EXTERNAL_TOOL_NAMES.search,
|
|
438
455
|
EXTERNAL_TOOL_NAMES.describe,
|
|
@@ -10,11 +10,13 @@ function buildWorkflowChildTools(childOptions) {
|
|
|
10
10
|
getCurrentContext: () => null,
|
|
11
11
|
getConfig: childOptions.getConfig,
|
|
12
12
|
getPrimaryModel: () => childOptions.model,
|
|
13
|
+
endpointTools: childOptions.endpointTools,
|
|
13
14
|
toolExecutorConfig: childOptions.toolExecutorConfig
|
|
14
15
|
}).createAllTools({
|
|
15
16
|
workspace: childOptions.workspace,
|
|
16
17
|
getPrimaryModel: () => childOptions.model,
|
|
17
18
|
agentId: childOptions.agentId,
|
|
19
|
+
sessionKey: childOptions.browserSessionKey,
|
|
18
20
|
disabledTools: new Set([
|
|
19
21
|
EXTERNAL_TOOL_NAMES.search,
|
|
20
22
|
EXTERNAL_TOOL_NAMES.describe,
|
|
@@ -9,7 +9,7 @@ export type BrowserNativeBootstrap = {
|
|
|
9
9
|
export type BrowserNativeBootstrapResult = {
|
|
10
10
|
ok: true;
|
|
11
11
|
gatewayUrl: string;
|
|
12
|
-
|
|
12
|
+
invitation: string;
|
|
13
13
|
expiresAt: number;
|
|
14
14
|
};
|
|
15
15
|
export declare function encodeNativeMessage(value: unknown): Buffer;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { closeXopcDatabase, openXopcDatabase } from "../storage/sqlite/connection.js";
|
|
2
|
+
import { devicePairingInvitationPayloadSchema, formatBrowserPairingInvitation } from "../../packages/gateway-contract/src/device-pairing.js";
|
|
2
3
|
import { getGatewayIdentityPublicKeyRaw, getOrCreateGatewayIdentity } from "../storage/sqlite/gateway-identity-repository.js";
|
|
3
4
|
import { createDevicePairingSetup } from "../storage/sqlite/device-pairing-repository.js";
|
|
4
5
|
import { init_loader, loadConfig } from "../config/loader.js";
|
|
@@ -49,7 +50,7 @@ function createBrowserNativeBootstrap(configPath, request) {
|
|
|
49
50
|
return {
|
|
50
51
|
ok: true,
|
|
51
52
|
gatewayUrl,
|
|
52
|
-
|
|
53
|
+
invitation: formatBrowserPairingInvitation(Buffer.from(JSON.stringify(devicePairingInvitationPayloadSchema.parse({
|
|
53
54
|
version: 3,
|
|
54
55
|
pairingToken: setup.token,
|
|
55
56
|
gatewayId: identity.id,
|
|
@@ -58,7 +59,7 @@ function createBrowserNativeBootstrap(configPath, request) {
|
|
|
58
59
|
routes: [route],
|
|
59
60
|
targetKind: setup.targetKind,
|
|
60
61
|
expiresAt: setup.expiresAt
|
|
61
|
-
})).toString("base64url")
|
|
62
|
+
}))).toString("base64url")),
|
|
62
63
|
expiresAt: setup.expiresAt
|
|
63
64
|
};
|
|
64
65
|
} finally {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Fixed path — gateway upgrades overwrite in place so Chrome sideload paths stay stable.
|
|
4
4
|
*/
|
|
5
5
|
import type { Config } from '../../config/schema.js';
|
|
6
|
-
export declare const BROWSER_EXT_REQUIRED_FILES: readonly ["manifest.json", "dist/background.js", "dist/sidepanel.html"];
|
|
6
|
+
export declare const BROWSER_EXT_REQUIRED_FILES: readonly ["manifest.json", "dist/background.js", "dist/sidepanel.html", "_locales/en/messages.json", "_locales/zh_CN/messages.json"];
|
|
7
7
|
export type BrowserExtBundledFrom = 'npm-dist' | 'git-dev' | 'electron-asar' | 'env-override';
|
|
8
8
|
export interface BrowserExtInstallMeta {
|
|
9
9
|
xopcVersion: string;
|
|
@@ -30,12 +30,15 @@ const STAGING_MAX_AGE_MS = 3600 * 1e3;
|
|
|
30
30
|
const INSTALLED_ARTIFACT_NAMES = [
|
|
31
31
|
"manifest.json",
|
|
32
32
|
"dist",
|
|
33
|
-
"icons"
|
|
33
|
+
"icons",
|
|
34
|
+
"_locales"
|
|
34
35
|
];
|
|
35
36
|
const BROWSER_EXT_REQUIRED_FILES = [
|
|
36
37
|
"manifest.json",
|
|
37
38
|
"dist/background.js",
|
|
38
|
-
"dist/sidepanel.html"
|
|
39
|
+
"dist/sidepanel.html",
|
|
40
|
+
"_locales/en/messages.json",
|
|
41
|
+
"_locales/zh_CN/messages.json"
|
|
39
42
|
];
|
|
40
43
|
function moduleDir() {
|
|
41
44
|
return dirname(fileURLToPath(import.meta.url));
|
|
@@ -53,7 +56,8 @@ function browserExtContentHash(dir) {
|
|
|
53
56
|
const relativePaths = [
|
|
54
57
|
"manifest.json",
|
|
55
58
|
...collectFiles("dist"),
|
|
56
|
-
...collectFiles("icons")
|
|
59
|
+
...collectFiles("icons"),
|
|
60
|
+
...collectFiles("_locales")
|
|
57
61
|
].sort();
|
|
58
62
|
for (const relativePath of relativePaths) {
|
|
59
63
|
hash.update(relativePath);
|
|
@@ -220,6 +224,7 @@ function copyBundledTree(src, dest) {
|
|
|
220
224
|
mkdirSync(dest, { recursive: true });
|
|
221
225
|
copyBundledFile(join(src, "manifest.json"), join(dest, "manifest.json"));
|
|
222
226
|
copyBundledDirectory(join(src, "dist"), join(dest, "dist"));
|
|
227
|
+
copyBundledDirectory(join(src, "_locales"), join(dest, "_locales"));
|
|
223
228
|
for (const icon of BROWSER_EXT_ICON_FILES) {
|
|
224
229
|
const iconSrc = join(src, "icons", icon);
|
|
225
230
|
if (existsSync(iconSrc)) copyBundledFile(iconSrc, join(dest, "icons", icon));
|
|
@@ -21,9 +21,12 @@ export declare class RealtimeExtensionBrowserProvider {
|
|
|
21
21
|
expectedProtocolVersion: number;
|
|
22
22
|
extensionVersion: string;
|
|
23
23
|
principalId: string;
|
|
24
|
+
endpointCount: number;
|
|
25
|
+
selectionRequired: boolean;
|
|
24
26
|
transport: "gateway-realtime";
|
|
25
27
|
};
|
|
26
28
|
shutdown(): Promise<void>;
|
|
27
29
|
private endpointForInput;
|
|
28
|
-
private
|
|
30
|
+
private defaultEndpoint;
|
|
31
|
+
private browserEndpoints;
|
|
29
32
|
}
|
|
@@ -14,14 +14,18 @@ var RealtimeExtensionBrowserProvider = class {
|
|
|
14
14
|
async start() {}
|
|
15
15
|
async waitForConnection(timeoutMs = this.connectionTimeout) {
|
|
16
16
|
const deadline = Date.now() + timeoutMs;
|
|
17
|
-
while (
|
|
17
|
+
while (this.browserEndpoints().length === 0) {
|
|
18
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
19
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
async send(input, timeoutMs = this.commandTimeout, visualFallback = true) {
|
|
23
23
|
const endpoint = this.endpointForInput(input);
|
|
24
|
-
if (!endpoint)
|
|
24
|
+
if (!endpoint) {
|
|
25
|
+
if (input.target?.kind === "endpoint") throw new Error(`The Chrome endpoint bound to this Session is offline (${input.target.endpointId}). Reconnect that browser or bind another one.`);
|
|
26
|
+
if (this.browserEndpoints().length > 1) throw new Error("Multiple Chrome extensions are connected. Attach this Session to a tab or choose a browser device first.");
|
|
27
|
+
throw new Error("Extension not connected to this Gateway. Open the xopc Chrome extension and connect it first.");
|
|
28
|
+
}
|
|
25
29
|
const tool = this.runtime.registry.getTool(endpoint.endpointId, BROWSER_CONTROL_ENDPOINT_TOOL_NAME);
|
|
26
30
|
if (!tool) throw new Error("Connected browser extension does not support authenticated Realtime browser control. Reload or update the extension.");
|
|
27
31
|
const id = crypto.randomUUID();
|
|
@@ -51,17 +55,20 @@ var RealtimeExtensionBrowserProvider = class {
|
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
isConnected() {
|
|
54
|
-
return
|
|
58
|
+
return this.browserEndpoints().length > 0;
|
|
55
59
|
}
|
|
56
60
|
getConnectionStatus() {
|
|
57
|
-
const
|
|
61
|
+
const endpoints = this.browserEndpoints();
|
|
62
|
+
const endpoint = endpoints.length === 1 ? endpoints[0] : void 0;
|
|
58
63
|
return {
|
|
59
|
-
socketConnected:
|
|
60
|
-
connected:
|
|
61
|
-
protocolVersion:
|
|
64
|
+
socketConnected: endpoints.length > 0,
|
|
65
|
+
connected: endpoints.length > 0,
|
|
66
|
+
protocolVersion: endpoints.length > 0 ? 4 : null,
|
|
62
67
|
expectedProtocolVersion: 4,
|
|
63
68
|
extensionVersion: endpoint?.appVersion ?? null,
|
|
64
69
|
principalId: endpoint?.principalId ?? null,
|
|
70
|
+
endpointCount: endpoints.length,
|
|
71
|
+
selectionRequired: endpoints.length > 1,
|
|
65
72
|
transport: "gateway-realtime"
|
|
66
73
|
};
|
|
67
74
|
}
|
|
@@ -73,10 +80,19 @@ var RealtimeExtensionBrowserProvider = class {
|
|
|
73
80
|
if (!binding || !endpoint || endpoint.principalId !== binding.principalId) return void 0;
|
|
74
81
|
return endpoint;
|
|
75
82
|
}
|
|
76
|
-
|
|
83
|
+
if (input.target?.kind === "endpoint") {
|
|
84
|
+
const endpoint = this.runtime.registry.get(input.target.endpointId);
|
|
85
|
+
if (!endpoint || endpoint.kind !== "browser" || !endpoint.tools.some((tool) => tool.descriptor.name === "browser.control")) return;
|
|
86
|
+
return endpoint;
|
|
87
|
+
}
|
|
88
|
+
return this.defaultEndpoint();
|
|
89
|
+
}
|
|
90
|
+
defaultEndpoint() {
|
|
91
|
+
const endpoints = this.browserEndpoints();
|
|
92
|
+
return endpoints.length === 1 ? endpoints[0] : void 0;
|
|
77
93
|
}
|
|
78
|
-
|
|
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)
|
|
94
|
+
browserEndpoints() {
|
|
95
|
+
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);
|
|
80
96
|
}
|
|
81
97
|
};
|
|
82
98
|
//#endregion
|
|
@@ -12,4 +12,7 @@ export declare class BrowserNotReadyError extends Error {
|
|
|
12
12
|
constructor(hint: BrowserSetupHint);
|
|
13
13
|
}
|
|
14
14
|
export declare function buildBrowserSetupDeepLink(driver: BrowserDriverKind): string;
|
|
15
|
-
export declare function checkBrowserReadiness(cfg: Config | undefined
|
|
15
|
+
export declare function checkBrowserReadiness(cfg: Config | undefined, options?: {
|
|
16
|
+
extensionConnected?: boolean;
|
|
17
|
+
checkExtensionInstall?: boolean;
|
|
18
|
+
}): Promise<BrowserNotReadyError | null>;
|
|
@@ -14,17 +14,23 @@ var BrowserNotReadyError = class extends Error {
|
|
|
14
14
|
function buildBrowserSetupDeepLink(driver) {
|
|
15
15
|
return `/settings/agent-browser?driver=${driver}`;
|
|
16
16
|
}
|
|
17
|
-
async function checkBrowserReadiness(cfg) {
|
|
17
|
+
async function checkBrowserReadiness(cfg, options = {}) {
|
|
18
18
|
if (!cfg?.browser.enabled) return null;
|
|
19
19
|
const driver = cfg.browser.driver;
|
|
20
20
|
let detail = null;
|
|
21
21
|
let reason = null;
|
|
22
22
|
try {
|
|
23
23
|
if (driver.kind === "extension") {
|
|
24
|
-
|
|
25
|
-
if (
|
|
26
|
-
reason = "
|
|
27
|
-
detail = "Chrome extension
|
|
24
|
+
if (options.extensionConnected) return null;
|
|
25
|
+
if (options.checkExtensionInstall === false) {
|
|
26
|
+
reason = "extension_not_connected";
|
|
27
|
+
detail = "No authenticated Chrome extension endpoint is connected.";
|
|
28
|
+
} else {
|
|
29
|
+
const { browserExtDoctor } = await import("./providers/browser-ext-install.js");
|
|
30
|
+
if (!(await browserExtDoctor()).installed) {
|
|
31
|
+
reason = "extension_not_installed";
|
|
32
|
+
detail = "Chrome extension artifacts are not installed.";
|
|
33
|
+
}
|
|
28
34
|
}
|
|
29
35
|
} else if (driver.kind === "playwright") if (driver.executablePath) await access(driver.executablePath);
|
|
30
36
|
else {
|
|
@@ -4,8 +4,8 @@ import { saveConfig } from "../../config/loader.js";
|
|
|
4
4
|
import { resolveGatewayLocalClientHost } from "../../config/gateway-bind.js";
|
|
5
5
|
import { formatExamples, register } from "../registry.js";
|
|
6
6
|
import "../../config/index.js";
|
|
7
|
-
import { provisionEagerRuntimes } from "../../runtime-tools/bootstrap.js";
|
|
8
7
|
import { colors } from "../utils/colors.js";
|
|
8
|
+
import { provisionEagerRuntimes } from "../../runtime-tools/bootstrap.js";
|
|
9
9
|
import { initWorkspace } from "../utils/init-workspace.js";
|
|
10
10
|
import { setupModel } from "./onboard/model.js";
|
|
11
11
|
import { getChannelConfigurators } from "./onboard/channels/registry.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Config } from '../config/schema.js';
|
|
2
|
+
import type { EndpointToolRuntime } from '../endpoint-tools/index.js';
|
|
2
3
|
import type { MessageBus } from '../infra/bus/index.js';
|
|
3
4
|
import type { SessionStore } from '../session/store.js';
|
|
4
5
|
export interface GatewayWorkflowAgentSurface {
|
|
@@ -10,6 +11,7 @@ export interface GatewayWorkflowHost {
|
|
|
10
11
|
readonly currentWorkspacePath: string;
|
|
11
12
|
readonly messageBusInstance: MessageBus;
|
|
12
13
|
readonly agentService: GatewayWorkflowAgentSurface;
|
|
14
|
+
readonly endpointTools?: EndpointToolRuntime;
|
|
13
15
|
emit(event: string, payload: unknown): void;
|
|
14
16
|
readonly sessionIndexInstance: {
|
|
15
17
|
getStore(): SessionStore;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createLogger } from "../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
3
|
import { shouldSilence, stripHeartbeatToken } from "../../heartbeat/tokens.js";
|
|
4
|
-
import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
|
|
5
4
|
import { isWithinActiveHours } from "../../heartbeat/active-hours.js";
|
|
6
5
|
import { isHeartbeatContentEmpty } from "../../heartbeat/content-check.js";
|
|
6
|
+
import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
|
|
7
7
|
import { createHeartbeatWake } from "../../heartbeat/wake.js";
|
|
8
8
|
import { resolveHeartbeatMdPath } from "../workspace-heartbeat-path.js";
|
|
9
9
|
import { readFile } from "fs/promises";
|
|
@@ -5,8 +5,8 @@ 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";
|
|
9
8
|
import { maxSessionInputRequestBodyBytes } from "../chat-limits.js";
|
|
9
|
+
import { BROWSER_EXTENSION_ID } from "../../browser/extension-identity.js";
|
|
10
10
|
import { isLoopbackIpAddress, isTrustedProxyAddress } from "../client-ip.js";
|
|
11
11
|
import { buildGatewayConsoleCspHeader } from "../security/csp.js";
|
|
12
12
|
import { checkBrowserOrigin, isChromeExtensionOrigin } from "../security/origin-check.js";
|
|
@@ -217,7 +217,10 @@ function createHonoApp(config) {
|
|
|
217
217
|
});
|
|
218
218
|
registerPublicGatewayRoutes(app, service);
|
|
219
219
|
registerPublicHtmlPreviewRoute(app);
|
|
220
|
-
registerDeviceAuthPublicRoutes(app)
|
|
220
|
+
registerDeviceAuthPublicRoutes(app, { getTrustedProxyContext: () => ({
|
|
221
|
+
trustedProxies: service.currentConfig.gateway?.trustedProxies,
|
|
222
|
+
allowRealIpFallback: service.currentConfig.gateway?.allowRealIpFallback === true
|
|
223
|
+
}) });
|
|
221
224
|
registerPublicExtensionAssetRoutes(app, service);
|
|
222
225
|
registerPublicLocalAppPreviewRoutes(app, service);
|
|
223
226
|
const authenticated = new Hono();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { parseTurnContextRefs } from "../../../agent/source-context/types.js";
|
|
2
1
|
import { validateWebchatAttachments, validateWebchatContent } from "../../chat-limits.js";
|
|
2
|
+
import { parseTurnContextRefs } from "../../../agent/source-context/types.js";
|
|
3
3
|
import { replaceLatestSessionTurn, submitSessionInput } from "./session-input-handler.js";
|
|
4
4
|
//#region src/gateway/hono/routes/agent-stream.ts
|
|
5
5
|
function registerAgentStreamRoutes(authenticated, deps) {
|
|
@@ -10,12 +10,12 @@ import { getGatewayPrincipal } from "../../security/gateway-principal.js";
|
|
|
10
10
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
11
11
|
//#region src/gateway/hono/routes/browser.ts
|
|
12
12
|
/** Browser Control status, approvals, and driver diagnostics. */
|
|
13
|
-
async function extensionStatus(service) {
|
|
14
|
-
const
|
|
15
|
-
const
|
|
13
|
+
async function extensionStatus(service, includeLocalArtifacts) {
|
|
14
|
+
const endpoints = service.endpointTools.registry.list().filter((item) => item.kind === "browser" && item.tools.some((tool) => tool.descriptor.name === "browser.control")).sort((left, right) => right.lastHeartbeatAt - left.lastHeartbeatAt);
|
|
15
|
+
const endpoint = endpoints[0];
|
|
16
|
+
const connected = endpoints.length > 0;
|
|
16
17
|
const extensionVersion = endpoint?.appVersion ?? null;
|
|
17
|
-
const
|
|
18
|
-
const doctor = await browserExtDoctor({ runtimeExtensionVersion: extensionVersion ?? void 0 });
|
|
18
|
+
const doctor = includeLocalArtifacts ? await import("../../../browser/providers/browser-ext-install.js").then(({ browserExtDoctor }) => browserExtDoctor({ runtimeExtensionVersion: extensionVersion ?? void 0 })) : void 0;
|
|
19
19
|
return {
|
|
20
20
|
running: true,
|
|
21
21
|
socketConnected: connected,
|
|
@@ -24,7 +24,15 @@ async function extensionStatus(service) {
|
|
|
24
24
|
protocolVersion: connected ? 4 : null,
|
|
25
25
|
expectedProtocolVersion: 4,
|
|
26
26
|
extensionVersion,
|
|
27
|
-
|
|
27
|
+
endpoints: endpoints.map((item) => ({
|
|
28
|
+
endpointId: item.endpointId,
|
|
29
|
+
principalId: item.principalId,
|
|
30
|
+
displayName: item.displayName,
|
|
31
|
+
platform: item.platform,
|
|
32
|
+
appVersion: item.appVersion,
|
|
33
|
+
lastHeartbeatAt: item.lastHeartbeatAt
|
|
34
|
+
})),
|
|
35
|
+
artifacts: { ...doctor ? {
|
|
28
36
|
installed: doctor.installed,
|
|
29
37
|
bundledAvailable: doctor.bundledAvailable,
|
|
30
38
|
extensionDir: doctor.extensionDir,
|
|
@@ -33,9 +41,9 @@ async function extensionStatus(service) {
|
|
|
33
41
|
needsRefresh: doctor.needsRefresh,
|
|
34
42
|
needsChromeReload: doctor.needsChromeReload,
|
|
35
43
|
nativeHost: doctor.nativeHost
|
|
36
|
-
},
|
|
44
|
+
} : {} },
|
|
37
45
|
bridgeHeld: false,
|
|
38
|
-
refCount:
|
|
46
|
+
refCount: endpoints.length,
|
|
39
47
|
transport: "gateway-realtime"
|
|
40
48
|
};
|
|
41
49
|
}
|
|
@@ -148,7 +156,7 @@ function registerBrowserRoutes(authenticated, deps) {
|
|
|
148
156
|
});
|
|
149
157
|
authenticated.get("/api/browser/extension-status", async (c) => {
|
|
150
158
|
try {
|
|
151
|
-
return c.json(await extensionStatus(service));
|
|
159
|
+
return c.json(await extensionStatus(service, isLocalOwnerRequest(c, service)));
|
|
152
160
|
} catch (error) {
|
|
153
161
|
return c.json({
|
|
154
162
|
running: false,
|
|
@@ -160,6 +168,7 @@ function registerBrowserRoutes(authenticated, deps) {
|
|
|
160
168
|
});
|
|
161
169
|
authenticated.get("/api/browser/status", async (c) => {
|
|
162
170
|
const config = service.currentConfig.browser;
|
|
171
|
+
const localManagementAvailable = isLocalOwnerRequest(c, service);
|
|
163
172
|
if (!config.enabled) return c.json({
|
|
164
173
|
ok: true,
|
|
165
174
|
payload: {
|
|
@@ -168,15 +177,19 @@ function registerBrowserRoutes(authenticated, deps) {
|
|
|
168
177
|
state: "disabled"
|
|
169
178
|
}
|
|
170
179
|
});
|
|
171
|
-
const
|
|
172
|
-
const driverStatus = config.driver.kind === "extension" ? await extensionStatus(service).catch((error) => ({ error: error instanceof Error ? error.message : String(error) })) : config.driver.kind === "playwright" ? await import("../../../browser/providers/playwright-doctor.js").then(({ playwrightChromiumDoctor }) => playwrightChromiumDoctor()) : void 0;
|
|
180
|
+
const driverStatus = config.driver.kind === "extension" ? await extensionStatus(service, localManagementAvailable).catch((error) => ({ error: error instanceof Error ? error.message : String(error) })) : config.driver.kind === "playwright" ? await import("../../../browser/providers/playwright-doctor.js").then(({ playwrightChromiumDoctor }) => playwrightChromiumDoctor()) : void 0;
|
|
173
181
|
const extensionConnected = config.driver.kind === "extension" ? driverStatus.connected === true : true;
|
|
182
|
+
const readiness = await checkBrowserReadiness(service.currentConfig, {
|
|
183
|
+
extensionConnected,
|
|
184
|
+
checkExtensionInstall: localManagementAvailable
|
|
185
|
+
});
|
|
174
186
|
const extensionProtocolDetail = config.driver.kind === "extension" && "socketConnected" in driverStatus && driverStatus.socketConnected && !driverStatus.connected ? `Chrome extension protocol ${driverStatus.protocolVersion ?? "unknown"} does not match ${driverStatus.expectedProtocolVersion ?? "the gateway"}. Reload the extension in Chrome.` : void 0;
|
|
175
187
|
return c.json({
|
|
176
188
|
ok: true,
|
|
177
189
|
payload: {
|
|
178
190
|
enabled: true,
|
|
179
191
|
driverKind: config.driver.kind,
|
|
192
|
+
localManagementAvailable,
|
|
180
193
|
state: !readiness && extensionConnected ? "ready" : "needs_attention",
|
|
181
194
|
reason: readiness?.hint.reason ?? (extensionConnected ? void 0 : "extension_not_connected"),
|
|
182
195
|
detail: readiness?.hint.detail ?? extensionProtocolDetail,
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
import type { Hono } from 'hono';
|
|
2
|
-
export
|
|
1
|
+
import type { Context, Hono } from 'hono';
|
|
2
|
+
export type PairingPublicRouteOptions = {
|
|
3
|
+
getTrustedProxyContext?: () => {
|
|
4
|
+
trustedProxies?: string[];
|
|
5
|
+
allowRealIpFallback?: boolean;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export declare function pairingClientKey(c: Context, options?: PairingPublicRouteOptions): string;
|
|
9
|
+
export declare function registerPairingApprovalPublicRoutes(app: Hono, options?: PairingPublicRouteOptions): void;
|
|
3
10
|
export declare function registerPairingApprovalAdminRoutes(app: Hono): void;
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
import { devicePairingCompleteSchema, devicePairingProofSchema, devicePairingRequestSchema } from "../../../../packages/gateway-contract/src/device-pairing.js";
|
|
2
2
|
import { getOrCreateGatewayIdentity, signGatewayPayload } from "../../../storage/sqlite/gateway-identity-repository.js";
|
|
3
|
+
import { resolveClientIpFromRequest } from "../../client-ip.js";
|
|
4
|
+
import { buckets } from "../../rate-limit/buckets.js";
|
|
5
|
+
import "../../rate-limit/index.js";
|
|
3
6
|
import { DevicePairingError, cancelDevicePairingSetup, decideDevicePairingRequest, operateDevicePairingRequest, readDevicePairingSetup, submitDevicePairingRequest } from "../../../storage/sqlite/device-pairing-approval.js";
|
|
4
7
|
import os from "node:os";
|
|
5
8
|
import { z } from "zod";
|
|
9
|
+
import { bodyLimit } from "hono/body-limit";
|
|
10
|
+
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
6
11
|
//#region src/gateway/hono/routes/device-pairing-approval.ts
|
|
12
|
+
function pairingClientKey(c, options) {
|
|
13
|
+
let remoteAddress;
|
|
14
|
+
try {
|
|
15
|
+
remoteAddress = getConnInfo(c).remote.address;
|
|
16
|
+
} catch {
|
|
17
|
+
remoteAddress = void 0;
|
|
18
|
+
}
|
|
19
|
+
const proxy = options?.getTrustedProxyContext?.();
|
|
20
|
+
return resolveClientIpFromRequest({
|
|
21
|
+
remoteAddress,
|
|
22
|
+
getHeader: (name) => c.req.header(name),
|
|
23
|
+
trustedProxies: proxy?.trustedProxies,
|
|
24
|
+
allowRealIpFallback: proxy?.allowRealIpFallback
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function pairingBlocked(c, key) {
|
|
28
|
+
const blocked = buckets.pairingExchange().check(key);
|
|
29
|
+
if (!blocked.blocked) return void 0;
|
|
30
|
+
c.header("Retry-After", String(blocked.retryAfterSec));
|
|
31
|
+
return c.json({
|
|
32
|
+
ok: false,
|
|
33
|
+
error: {
|
|
34
|
+
code: "PAIRING_RATE_LIMITED",
|
|
35
|
+
message: "Too many failed pairing attempts"
|
|
36
|
+
}
|
|
37
|
+
}, 429);
|
|
38
|
+
}
|
|
7
39
|
function pairingError(c, error) {
|
|
8
40
|
if (error instanceof DevicePairingError) return c.json({
|
|
9
41
|
ok: false,
|
|
@@ -20,7 +52,7 @@ function pairingError(c, error) {
|
|
|
20
52
|
}
|
|
21
53
|
}, 500);
|
|
22
54
|
}
|
|
23
|
-
function registerPairingApprovalPublicRoutes(app) {
|
|
55
|
+
function registerPairingApprovalPublicRoutes(app, options) {
|
|
24
56
|
for (const action of [
|
|
25
57
|
"request",
|
|
26
58
|
"status",
|
|
@@ -28,15 +60,21 @@ function registerPairingApprovalPublicRoutes(app) {
|
|
|
28
60
|
"cancel"
|
|
29
61
|
]) {
|
|
30
62
|
const path = action === "request" ? "/api/device-pairing/requests" : `/api/device-pairing/requests/:id/${action}`;
|
|
31
|
-
app.post(path, async (c) => {
|
|
63
|
+
app.post(path, bodyLimit({ maxSize: 4 * 1024 }), async (c) => {
|
|
64
|
+
const clientKey = pairingClientKey(c, options);
|
|
65
|
+
const blocked = pairingBlocked(c, clientKey);
|
|
66
|
+
if (blocked) return blocked;
|
|
32
67
|
const parsed = (action === "request" ? devicePairingRequestSchema : action === "complete" ? devicePairingCompleteSchema : devicePairingProofSchema).safeParse(await c.req.json().catch(() => null));
|
|
33
|
-
if (!parsed.success || action !== "request" && parsed.data.requestId !== c.req.param("id"))
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
68
|
+
if (!parsed.success || action !== "request" && parsed.data.requestId !== c.req.param("id")) {
|
|
69
|
+
buckets.pairingExchange().fail(clientKey);
|
|
70
|
+
return c.json({
|
|
71
|
+
ok: false,
|
|
72
|
+
error: {
|
|
73
|
+
code: "BAD_REQUEST",
|
|
74
|
+
message: "Invalid pairing request"
|
|
75
|
+
}
|
|
76
|
+
}, 400);
|
|
77
|
+
}
|
|
40
78
|
try {
|
|
41
79
|
const result = action === "request" ? { request: submitDevicePairingRequest(parsed.data) } : operateDevicePairingRequest(action, parsed.data);
|
|
42
80
|
const signedPayload = Buffer.from(JSON.stringify({
|
|
@@ -48,12 +86,14 @@ function registerPairingApprovalPublicRoutes(app) {
|
|
|
48
86
|
nonce: parsed.data.nonce,
|
|
49
87
|
issuedAt: Date.now()
|
|
50
88
|
})).toString("base64url");
|
|
89
|
+
buckets.pairingExchange().succeed(clientKey);
|
|
51
90
|
return c.json({
|
|
52
91
|
ok: true,
|
|
53
92
|
signedPayload,
|
|
54
93
|
signature: signGatewayPayload(signedPayload)
|
|
55
94
|
});
|
|
56
95
|
} catch (error) {
|
|
96
|
+
buckets.pairingExchange().fail(clientKey);
|
|
57
97
|
return pairingError(c, error);
|
|
58
98
|
}
|
|
59
99
|
});
|
|
@@ -97,4 +137,4 @@ function registerPairingApprovalAdminRoutes(app) {
|
|
|
97
137
|
});
|
|
98
138
|
}
|
|
99
139
|
//#endregion
|
|
100
|
-
export { registerPairingApprovalAdminRoutes, registerPairingApprovalPublicRoutes };
|
|
140
|
+
export { pairingClientKey, registerPairingApprovalAdminRoutes, registerPairingApprovalPublicRoutes };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import type { AuthenticatedRouteDeps } from './deps.js';
|
|
3
|
-
|
|
3
|
+
import { type PairingPublicRouteOptions } from './device-pairing-approval.js';
|
|
4
|
+
export declare function registerDeviceAuthPublicRoutes(app: Hono, options?: PairingPublicRouteOptions): void;
|
|
4
5
|
export declare function registerDeviceRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void;
|