@xopcai/xopc 0.0.272 → 0.0.274
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -5
- package/README.zh-CN.md +1 -5
- package/dist/browser-ext/dist/assets/{auth-C1QI-uMG.js → auth-O8RiOF3l.js} +265 -100
- package/dist/browser-ext/dist/background.js +1 -22
- package/dist/browser-ext/dist/sidepanel.html +1 -1
- package/dist/browser-ext/dist/sidepanel.js +68 -36
- package/dist/browser-ext/manifest.json +1 -12
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-DffqjeqQ.js → activity-store-SJZ5fS17.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Cwj4HCYY.js → agent-avatar-dicebear-BckaANxj.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-6CLfLxVc.js → agent-defaults-settings-panel-B8eyC6K1.js} +1 -1
- package/dist/gateway/static/root/assets/agents-CSWj_yo1.js +1 -0
- package/dist/gateway/static/root/assets/{api-CGLdye4i.js → api-CnJAVHBu.js} +1 -1
- package/dist/gateway/static/root/assets/app-link-BiBXVEvp.js +1 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-BLujSbaw.js → app-management-settings-panel-DY-Zbgmt.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-BXMcueb8.js → appearance-settings-CoJR7ZRl.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-BEgy4hY3.js → apps-page-CSMvkuot.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-D4bUampf.js → archive-plugin-BwAlRT53.js} +1 -1
- package/dist/gateway/static/root/assets/attachment-load-R835uxgL.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-dQyolNmy.js → automations-page-BEZy0P_u.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-CpaI13J7.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-bbvp81h2.js → binary-plugins-CESFx3yb.js} +2 -2
- package/dist/gateway/static/root/assets/{block-editor-C8e6Wri4.js → block-editor-DDJcf2wR.js} +3 -3
- package/dist/gateway/static/root/assets/{browser-automation-inputs-oJWJtQa6.js → browser-automation-inputs-DGCqIPXT.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-CAcJQSjN.js → browser-automations-page-BH4lSfr5.js} +2 -2
- package/dist/gateway/static/root/assets/browser-settings-page-CbTooPd2.js +8 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-C4LZWHhv.js +8 -0
- package/dist/gateway/static/root/assets/channels-settings-BAljmSa7.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-CfOPNnVr.js → channels-status-swr-BTT2sIGg.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-9ZOl92kh.js → chat-terminal-dock-D8U97O95.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-CVRMjNNr.js +2 -0
- package/dist/gateway/static/root/assets/{date-picker-Dj6FRLF9.js → date-picker-7sDSMswL.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-D1q89tVc.js → dependency-picker-DMIC86ZW.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-DfqZBvhp.js → desktop-pet-BtdiY5jK.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-HCEh2MOm.js → desktop-pet-settings-D_B8azDH.js} +1 -1
- package/dist/gateway/static/root/assets/device-pairing-wizard-Dmh3Nh4d.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-C8cAWVoK.js → extension-debug-page-CoFKb01d.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-5N87Vaym.js → extension-page-CEkNsCAY.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-D85SAUFP.js → extension-settings-page-Bi2R8P5j.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-BjsDnB_n.js → gateway-config-swr-cd8owslz.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-q9p3sduo.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-BIf0eLWX.js → gateway-startup-retry-CVTa5HSS.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-BpDWGE1a.js → heartbeat-settings-DQtelFiU.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-COpGnTFZ.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-1m8xTVS_.js → image-generation-api-DtMRG1oF.js} +1 -1
- package/dist/gateway/static/root/assets/{index-C6PmD0cK.js → index-SxqsTD4i.js} +35 -35
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Byy8mCSq.js → keyboard-shortcuts-settings-BuYmZS1K.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-DV_17Zxc.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-BPo0wBgM.js → local-apps-page-CExnZyGZ.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-CRNble2Z.js → locale-store-BkJ7g35d.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-X5FdLlXK.js → logs-page-CA5ArMoN.js} +2 -2
- package/dist/gateway/static/root/assets/management-settings-1UiJPrkX.js +1 -0
- package/dist/gateway/static/root/assets/markdown-view-B2IR4E2r.js +201 -0
- package/dist/gateway/static/root/assets/{media-plugins-Bzka9qXu.js → media-plugins-nNFOk9HL.js} +1 -1
- package/dist/gateway/static/root/assets/{messages-DH_HCgmw.js → messages-BGc-OJWg.js} +2 -2
- package/dist/gateway/static/root/assets/note-detail-panel-B5OQ0fBY.js +5 -0
- package/dist/gateway/static/root/assets/notes-page-CVPPMVDl.js +8 -0
- package/dist/gateway/static/root/assets/{page-tabs-Ys0p65Lt.js → page-tabs-CRombigq.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-iknB5xEp.js → preview-open-alternatives-Bcuj43lk.js} +1 -1
- package/dist/gateway/static/root/assets/{proactive-card-BPZDgNNR.js → proactive-card-Cb3Xeoyx.js} +1 -1
- package/dist/gateway/static/root/assets/{proactive-page-CxdZR-p2.js → proactive-page-TbF9KVpm.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-W1o3f-kz.js → product-open-page-DsQcgZkI.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-B9OQ2l9x.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-DCqSTNkY.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-DmYFpjsH.js → registry-api-CTq8EECj.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-d9Fg70gX.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DP1XyYO8.js → runtime-tools-settings-panel-BudlvBu-.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-BMYKdzsH.js → schema-form-Ch3MEX4h.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-D8RiQ-ag.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-DVH_l6Hr.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-2LGjz7bu.js → settings-form-section-CIA1AA-A.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-7DjpC9_C.js → settings-page-B0Khoduh.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-Dj9jFWJt.js → setup-status-panel-COJFl4N_.js} +1 -1
- package/dist/gateway/static/root/assets/share-preview-page-Cj9XWg-X.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-_OuE6A0G.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-BM4Ezjbx.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-DNEScMZH.js → skills-page-Dyh1kf9t.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-q88l65YU.js → skills-page.utils-DsQZkMti.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-8NIssGKj.js → system-settings-panel-BSho_Tpk.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-B5HWM12k.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-BXZG3S25.js → theme-store-mV6Cm2vj.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-CWsRPM84.js → tunnel-api-4DUHxSQG.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-BwQuoTQU.js → use-autosave-BG8Yx7hj.js} +1 -1
- package/dist/gateway/static/root/assets/user-model-page-IjVyv5jj.js +2 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-Bd03Ic0s.js → voice-api-key-field-DYXwDhHq.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-gggUp3u9.js → voice-config-api-D3DdcH4H.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-DF8PR_pK.js → work-discovery-overlay-CQqjzaRo.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BiAAWlir.js → workflow-run-setup-panel-C5H-Fd-U.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-D1hMyPsY.js +6 -0
- package/dist/gateway/static/root/index.html +10 -9
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/device-pairing.js +41 -2
- package/dist/src/agent/service.js +20 -16
- 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 +6 -0
- package/dist/src/browser/providers/browser-ext-install.js +16 -1
- package/dist/src/cli/commands/gateway/run-foreground.js +8 -5
- package/dist/src/cli/commands/gateway/service.js +4 -3
- package/dist/src/cli/commands/gateway/token.js +28 -1
- package/dist/src/gateway/device-routes.js +4 -3
- 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 +26 -0
- 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/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/rate-limit/buckets.js +2 -1
- package/dist/src/gateway/service/run-gateway-agent.js +1 -1
- package/dist/src/storage/sqlite/device-pairing-approval.js +4 -4
- 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/package.json +3 -3
- package/dist/browser-ext/dist/pairing-handoff.js +0 -51
- package/dist/gateway/static/root/assets/agents-B6mu64fY.js +0 -1
- package/dist/gateway/static/root/assets/attachment-load-CBGNA2Ys.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-DxRFrmrV.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-TLAlqcPo.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-z-K_jrZz.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-Dzrk33aB.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-B1dIoRNi.js +0 -2
- package/dist/gateway/static/root/assets/device-pairing-wizard-CPth92s6.js +0 -1
- package/dist/gateway/static/root/assets/excel-worksheet-utils-EyvwNT4X.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-Dqx8PmcT.js +0 -1
- package/dist/gateway/static/root/assets/home-page-DJaqpC8-.js +0 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-l2AEiG2C.js +0 -2
- package/dist/gateway/static/root/assets/management-settings-DT6NoIjm.js +0 -1
- package/dist/gateway/static/root/assets/markdown-view-AglreSOj.js +0 -201
- package/dist/gateway/static/root/assets/note-detail-panel-BUJPoaGZ.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-BwFpWRuo.js +0 -8
- package/dist/gateway/static/root/assets/project-detail-page-BU1RKAcy.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-z0X0tL8G.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-NNTut2I8.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-B6SsHlz5.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-BQu8bAcN.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-BxYdSi8c.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-CMqE0KuK.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-BXqSJymN.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-L6dp9v2y.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-D4BucMrZ.js +0 -2
- package/dist/gateway/static/root/assets/workflows-page-DryKUMfA.js +0 -6
|
@@ -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 {
|
|
@@ -33,6 +33,10 @@ export interface EnsureBrowserExtResult {
|
|
|
33
33
|
xopcVersion: string;
|
|
34
34
|
copied: boolean;
|
|
35
35
|
}
|
|
36
|
+
export interface BrowserExtensionArchive {
|
|
37
|
+
filename: string;
|
|
38
|
+
data: Buffer;
|
|
39
|
+
}
|
|
36
40
|
export interface BrowserNativeHostInstallResult {
|
|
37
41
|
installed: boolean;
|
|
38
42
|
manifestPaths: string[];
|
|
@@ -50,6 +54,8 @@ export declare function resolveBundledBrowserExtDir(): Promise<{
|
|
|
50
54
|
dir: string;
|
|
51
55
|
bundledFrom: BrowserExtBundledFrom;
|
|
52
56
|
} | null>;
|
|
57
|
+
/** Build a portable archive for installing the bundled extension on another computer. */
|
|
58
|
+
export declare function createBrowserExtensionArchive(): Promise<BrowserExtensionArchive>;
|
|
53
59
|
export declare function computeNeedsRefresh(params: {
|
|
54
60
|
force?: boolean;
|
|
55
61
|
bundledManifestVersion: string;
|
|
@@ -14,6 +14,7 @@ import { fileURLToPath } from "node:url";
|
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
16
|
import { readFile, readdir, rm } from "node:fs/promises";
|
|
17
|
+
import AdmZip from "adm-zip";
|
|
17
18
|
//#region src/browser/providers/browser-ext-install.ts
|
|
18
19
|
/**
|
|
19
20
|
* Install bundled Chrome extension artifacts into {resolveBinDir()}/browser-ext/.
|
|
@@ -134,6 +135,20 @@ async function resolveBundledBrowserExtDir() {
|
|
|
134
135
|
}
|
|
135
136
|
return null;
|
|
136
137
|
}
|
|
138
|
+
/** Build a portable archive for installing the bundled extension on another computer. */
|
|
139
|
+
async function createBrowserExtensionArchive() {
|
|
140
|
+
const bundled = await resolveBundledBrowserExtDir();
|
|
141
|
+
if (!bundled) throw new Error("Bundled browser extension not found. Reinstall or rebuild xopc.");
|
|
142
|
+
const root = "xopc-browser-extension";
|
|
143
|
+
const zip = new AdmZip();
|
|
144
|
+
zip.addLocalFile(join(bundled.dir, "manifest.json"), root);
|
|
145
|
+
zip.addLocalFolder(join(bundled.dir, "dist"), `${root}/dist`);
|
|
146
|
+
zip.addLocalFolder(join(bundled.dir, "icons"), `${root}/icons`);
|
|
147
|
+
return {
|
|
148
|
+
filename: `xopc-browser-extension-${PACKAGE_VERSION}.zip`,
|
|
149
|
+
data: zip.toBuffer()
|
|
150
|
+
};
|
|
151
|
+
}
|
|
137
152
|
function computeNeedsRefresh(params) {
|
|
138
153
|
if (params.force) return true;
|
|
139
154
|
if (!params.installedPath || !validateBrowserExtLayout(params.installedPath)) return true;
|
|
@@ -471,4 +486,4 @@ async function openBrowserExtensionInstallUi(opts) {
|
|
|
471
486
|
};
|
|
472
487
|
}
|
|
473
488
|
//#endregion
|
|
474
|
-
export { BROWSER_EXT_REQUIRED_FILES, browserExtContentHash, browserExtDoctor, browserNativeHostDoctor, browserNativeManifestDirectories, computeNeedsRefresh, ensureBrowserExtensionArtifacts, ensureBrowserExtensionOnStartup, installBrowserNativeMessagingHost, openBrowserExtensionInstallUi, readInstalledExtensionDir, resolveBundledBrowserExtDir, resolveInstalledExtensionPath, resolveWindowsExtensionManager, validateBrowserExtLayout };
|
|
489
|
+
export { BROWSER_EXT_REQUIRED_FILES, browserExtContentHash, browserExtDoctor, browserNativeHostDoctor, browserNativeManifestDirectories, computeNeedsRefresh, createBrowserExtensionArchive, ensureBrowserExtensionArtifacts, ensureBrowserExtensionOnStartup, installBrowserNativeMessagingHost, openBrowserExtensionInstallUi, readInstalledExtensionDir, resolveBundledBrowserExtDir, resolveInstalledExtensionPath, resolveWindowsExtensionManager, validateBrowserExtLayout };
|
|
@@ -69,10 +69,13 @@ async function runGatewayFromCliOptions(options, ctx) {
|
|
|
69
69
|
const { resolveGatewayAuth, assertGatewayAuthConfigured } = await import("../../../gateway/auth.js");
|
|
70
70
|
const { assertGatewayRuntimeConfig } = await import("../../../gateway/runtime-config.js");
|
|
71
71
|
let auth = resolveGatewayAuth({ authConfig: effectiveConfig.gateway?.auth });
|
|
72
|
-
if (typeof options.token === "string" && options.token.trim().length > 0)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
if (typeof options.token === "string" && options.token.trim().length > 0) {
|
|
73
|
+
auth = {
|
|
74
|
+
mode: "token",
|
|
75
|
+
token: options.token.trim()
|
|
76
|
+
};
|
|
77
|
+
process.env.XOPC_GATEWAY_TOKEN = auth.token;
|
|
78
|
+
}
|
|
76
79
|
try {
|
|
77
80
|
assertGatewayAuthConfigured(auth);
|
|
78
81
|
assertGatewayRuntimeConfig({
|
|
@@ -126,7 +129,7 @@ async function runGatewayFromCliOptions(options, ctx) {
|
|
|
126
129
|
});
|
|
127
130
|
await server.start();
|
|
128
131
|
const displayHost = bindHost === "0.0.0.0" ? "localhost" : bindHost;
|
|
129
|
-
const token =
|
|
132
|
+
const token = auth.mode === "token" ? auth.token : void 0;
|
|
130
133
|
console.log("✅ Gateway started");
|
|
131
134
|
console.log(` URL: http://${displayHost}:${port}`);
|
|
132
135
|
if (token) console.log(` Token: ${token.slice(0, 8)}...${token.slice(-8)}`);
|
|
@@ -9,8 +9,9 @@ init_paths();
|
|
|
9
9
|
* Create service install subcommand - actually installs the OS service
|
|
10
10
|
*/
|
|
11
11
|
function createInstallCommand() {
|
|
12
|
-
return new Command("install").description("Install gateway as OS service (LaunchAgent / systemd / Task)").option("--port <port>", "Gateway port").option("--token <token>", "Gateway auth token").option("--force", "Force reinstall if already installed").option("--json", "Output JSON").action(async (options) => {
|
|
12
|
+
return new Command("install").description("Install gateway as OS service (LaunchAgent / systemd / Task)").option("--port <port>", "Gateway port").option("--token <token>", "Gateway auth token").option("--force", "Force reinstall if already installed").option("--json", "Output JSON").action(async (options, command) => {
|
|
13
13
|
const configPath = getContextWithOpts().configPath || resolveConfigPath();
|
|
14
|
+
const force = options.force === true || command.optsWithGlobals().force === true;
|
|
14
15
|
const [{ loadConfig }, { resolveGatewayService, isDaemonAvailableAsync, getPlatformName }, { buildGatewayInstallArgs }] = await Promise.all([
|
|
15
16
|
import("../../../config/index.js"),
|
|
16
17
|
import("../../../daemon/service.js"),
|
|
@@ -30,7 +31,7 @@ function createInstallCommand() {
|
|
|
30
31
|
}
|
|
31
32
|
const service = await resolveGatewayService();
|
|
32
33
|
const loaded = await service.isLoaded({ env: process.env });
|
|
33
|
-
if (loaded && !
|
|
34
|
+
if (loaded && !force) {
|
|
34
35
|
if (options.json) console.log(JSON.stringify({
|
|
35
36
|
ok: true,
|
|
36
37
|
result: "already-installed"
|
|
@@ -38,7 +39,7 @@ function createInstallCommand() {
|
|
|
38
39
|
else console.log("ℹ️ Service already installed. Use --force to reinstall.");
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
|
-
if (loaded &&
|
|
42
|
+
if (loaded && force) try {
|
|
42
43
|
await service.uninstall({ env: process.env });
|
|
43
44
|
} catch {}
|
|
44
45
|
const installArgs = buildGatewayInstallArgs({
|
|
@@ -4,6 +4,17 @@ import crypto from "crypto";
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
//#region src/cli/commands/gateway/token.ts
|
|
6
6
|
init_paths();
|
|
7
|
+
async function readInstalledServiceToken() {
|
|
8
|
+
try {
|
|
9
|
+
const { isDaemonAvailableAsync, resolveGatewayService } = await import("../../../daemon/service.js");
|
|
10
|
+
if (!await isDaemonAvailableAsync()) return void 0;
|
|
11
|
+
const service = await resolveGatewayService();
|
|
12
|
+
if (!await service.isLoaded({ env: process.env })) return void 0;
|
|
13
|
+
return (await service.readCommand(process.env))?.environment?.XOPC_GATEWAY_TOKEN?.trim() || void 0;
|
|
14
|
+
} catch {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
7
18
|
/**
|
|
8
19
|
* Create the token subcommand for managing gateway authentication token.
|
|
9
20
|
*/
|
|
@@ -14,6 +25,7 @@ function createTokenCommand() {
|
|
|
14
25
|
const log = createLogger("GatewayTokenCommand");
|
|
15
26
|
try {
|
|
16
27
|
const config = loadConfig(configPath);
|
|
28
|
+
const installedServiceToken = await readInstalledServiceToken();
|
|
17
29
|
if (options.generate) {
|
|
18
30
|
const newToken = crypto.randomBytes(24).toString("hex");
|
|
19
31
|
config.gateway = config.gateway || {};
|
|
@@ -31,9 +43,16 @@ function createTokenCommand() {
|
|
|
31
43
|
console.log("");
|
|
32
44
|
console.log("Or set environment variable:");
|
|
33
45
|
console.log(` export XOPC_GATEWAY_TOKEN=${newToken}`);
|
|
46
|
+
if (installedServiceToken && installedServiceToken !== newToken) {
|
|
47
|
+
console.log("");
|
|
48
|
+
console.log("⚠️ The installed gateway service still uses its previous token.");
|
|
49
|
+
console.log(" Run `xopc gateway service install --force`, then restart the service.");
|
|
50
|
+
}
|
|
34
51
|
process.exit(0);
|
|
35
52
|
} else {
|
|
36
|
-
const
|
|
53
|
+
const configToken = config.gateway?.auth?.token?.trim();
|
|
54
|
+
const environmentToken = process.env.XOPC_GATEWAY_TOKEN?.trim();
|
|
55
|
+
const currentToken = installedServiceToken || environmentToken || configToken;
|
|
37
56
|
if ((config.gateway?.auth?.mode || "token") === "none") {
|
|
38
57
|
console.log("⚠️ Gateway authentication is disabled (mode: none)");
|
|
39
58
|
console.log("");
|
|
@@ -46,6 +65,14 @@ function createTokenCommand() {
|
|
|
46
65
|
console.log(` ${currentToken}`);
|
|
47
66
|
console.log("");
|
|
48
67
|
console.log(`Preview: ${tokenPreview}`);
|
|
68
|
+
if (installedServiceToken) console.log("Source: installed gateway service");
|
|
69
|
+
else if (environmentToken) console.log("Source: XOPC_GATEWAY_TOKEN");
|
|
70
|
+
else console.log(`Source: ${configPath}`);
|
|
71
|
+
if (installedServiceToken && configToken && installedServiceToken !== configToken) {
|
|
72
|
+
console.log("");
|
|
73
|
+
console.log("⚠️ Token drift detected: the installed service token overrides the config token.");
|
|
74
|
+
console.log(" Run `xopc gateway service install --force`, then restart the service to sync.");
|
|
75
|
+
}
|
|
49
76
|
console.log("");
|
|
50
77
|
console.log("Usage:");
|
|
51
78
|
console.log(` xopc gateway --token ${currentToken}`);
|
|
@@ -19,9 +19,6 @@ function routeId(kind, url) {
|
|
|
19
19
|
function resolveSecureDeviceRoutes(config) {
|
|
20
20
|
const tunnelStatus = getTunnelService().getStatus();
|
|
21
21
|
const candidates = [{
|
|
22
|
-
kind: "custom-https",
|
|
23
|
-
url: secureUrl(resolveReverseProxyPublicUrl(config))
|
|
24
|
-
}, {
|
|
25
22
|
kind: "xopc-secure-link",
|
|
26
23
|
url: tunnelStatus.state === "connected" ? secureUrl(tunnelStatus.publicUrl) : null
|
|
27
24
|
}];
|
|
@@ -30,6 +27,10 @@ function resolveSecureDeviceRoutes(config) {
|
|
|
30
27
|
kind: "tailscale",
|
|
31
28
|
url: secureUrl(`https://${tailscale.hostname}`)
|
|
32
29
|
});
|
|
30
|
+
candidates.push({
|
|
31
|
+
kind: "custom-https",
|
|
32
|
+
url: secureUrl(resolveReverseProxyPublicUrl(config))
|
|
33
|
+
});
|
|
33
34
|
const seen = /* @__PURE__ */ new Set();
|
|
34
35
|
const routes = [];
|
|
35
36
|
for (const candidate of candidates) {
|
|
@@ -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) {
|
|
@@ -49,6 +49,10 @@ function isLocalOwnerRequest(c, service) {
|
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
function isOwnerRequest(c) {
|
|
53
|
+
const principal = getGatewayPrincipal(c);
|
|
54
|
+
return principal.kind === "owner" || principal.kind === "trusted-proxy";
|
|
55
|
+
}
|
|
52
56
|
function chromeDevicePrincipal(c) {
|
|
53
57
|
const principal = getGatewayPrincipal(c);
|
|
54
58
|
if (principal.kind !== "device" || !principal.deviceId) return void 0;
|
|
@@ -235,6 +239,28 @@ function registerBrowserRoutes(authenticated, deps) {
|
|
|
235
239
|
}, 500);
|
|
236
240
|
}
|
|
237
241
|
});
|
|
242
|
+
authenticated.get("/api/browser/extension/archive", strictRateLimitMiddleware, async (c) => {
|
|
243
|
+
if (!isOwnerRequest(c)) return c.json({
|
|
244
|
+
ok: false,
|
|
245
|
+
error: "Owner access required."
|
|
246
|
+
}, 403);
|
|
247
|
+
try {
|
|
248
|
+
const { createBrowserExtensionArchive } = await import("../../../browser/providers/browser-ext-install.js");
|
|
249
|
+
const archive = await createBrowserExtensionArchive();
|
|
250
|
+
const body = new Uint8Array(archive.data.byteLength);
|
|
251
|
+
body.set(archive.data);
|
|
252
|
+
return c.body(body, 200, {
|
|
253
|
+
"Content-Type": "application/zip",
|
|
254
|
+
"Content-Disposition": `attachment; filename="${archive.filename}"`,
|
|
255
|
+
"Cache-Control": "private, no-store"
|
|
256
|
+
});
|
|
257
|
+
} catch (error) {
|
|
258
|
+
return c.json({
|
|
259
|
+
ok: false,
|
|
260
|
+
error: error instanceof Error ? error.message : String(error)
|
|
261
|
+
}, 500);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
238
264
|
authenticated.post("/api/browser/extension/open", strictRateLimitMiddleware, async (c) => {
|
|
239
265
|
if (!isLocalOwnerRequest(c, service)) return c.json({
|
|
240
266
|
ok: false,
|
|
@@ -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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { devicePairingTargetKindSchema } from "../../../../packages/gateway-contract/src/device-pairing.js";
|
|
1
|
+
import { devicePairingInvitationPayloadSchema, devicePairingTargetKindSchema, formatBrowserPairingInvitation } from "../../../../packages/gateway-contract/src/device-pairing.js";
|
|
2
2
|
import { listDevices, revokeDevice, rotateDeviceRefreshToken } from "../../../storage/sqlite/device-access-repository.js";
|
|
3
3
|
import { getGatewayIdentityPublicKeyRaw, getOrCreateGatewayIdentity, signGatewayPayload } from "../../../storage/sqlite/gateway-identity-repository.js";
|
|
4
4
|
import { createDevicePairingSetup, isDevicePairingSetupActive } from "../../../storage/sqlite/device-pairing-repository.js";
|
|
@@ -6,7 +6,7 @@ import { resolveSecureDeviceRoutes } from "../../device-routes.js";
|
|
|
6
6
|
import { buckets } from "../../rate-limit/buckets.js";
|
|
7
7
|
import "../../rate-limit/index.js";
|
|
8
8
|
import { getGatewayPrincipal } from "../../security/gateway-principal.js";
|
|
9
|
-
import { registerPairingApprovalAdminRoutes, registerPairingApprovalPublicRoutes } from "./device-pairing-approval.js";
|
|
9
|
+
import { pairingClientKey, registerPairingApprovalAdminRoutes, registerPairingApprovalPublicRoutes } from "./device-pairing-approval.js";
|
|
10
10
|
import os from "node:os";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { bodyLimit } from "hono/body-limit";
|
|
@@ -21,10 +21,10 @@ const refreshRequestSchema = z.strictObject({
|
|
|
21
21
|
});
|
|
22
22
|
const pairingProbeSchema = z.strictObject({ pairingId: z.string().uuid() });
|
|
23
23
|
const pairingSetupSchema = z.strictObject({ targetKind: devicePairingTargetKindSchema });
|
|
24
|
-
function
|
|
25
|
-
return Buffer.from(JSON.stringify(input)).toString("base64url");
|
|
24
|
+
function pairingPayload(input) {
|
|
25
|
+
return Buffer.from(JSON.stringify(devicePairingInvitationPayloadSchema.parse(input))).toString("base64url");
|
|
26
26
|
}
|
|
27
|
-
function registerDeviceAuthPublicRoutes(app) {
|
|
27
|
+
function registerDeviceAuthPublicRoutes(app, options) {
|
|
28
28
|
app.post("/api/gateway-identity/challenge", bodyLimit({ maxSize: 512 }), async (c) => {
|
|
29
29
|
const budget = buckets.identityChallenge().consume("gateway");
|
|
30
30
|
if (!budget.allowed) {
|
|
@@ -46,7 +46,7 @@ function registerDeviceAuthPublicRoutes(app) {
|
|
|
46
46
|
signature: signGatewayPayload(signedPayload)
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
|
-
registerPairingApprovalPublicRoutes(app);
|
|
49
|
+
registerPairingApprovalPublicRoutes(app, options);
|
|
50
50
|
app.post("/api/device-auth/refresh", async (c) => {
|
|
51
51
|
const parsed = refreshRequestSchema.safeParse(await c.req.json().catch(() => null));
|
|
52
52
|
if (!parsed.success) return c.json({
|
|
@@ -81,21 +81,37 @@ function registerDeviceAuthPublicRoutes(app) {
|
|
|
81
81
|
}, 401);
|
|
82
82
|
}
|
|
83
83
|
});
|
|
84
|
-
app.post("/api/device-pairing/probe", async (c) => {
|
|
84
|
+
app.post("/api/device-pairing/probe", bodyLimit({ maxSize: 512 }), async (c) => {
|
|
85
|
+
const clientKey = pairingClientKey(c, options);
|
|
86
|
+
const blocked = buckets.pairingExchange().check(clientKey);
|
|
87
|
+
if (blocked.blocked) {
|
|
88
|
+
c.header("Retry-After", String(blocked.retryAfterSec));
|
|
89
|
+
return c.json({
|
|
90
|
+
ok: false,
|
|
91
|
+
error: {
|
|
92
|
+
code: "PAIRING_RATE_LIMITED",
|
|
93
|
+
message: "Too many failed pairing attempts"
|
|
94
|
+
}
|
|
95
|
+
}, 429);
|
|
96
|
+
}
|
|
85
97
|
const parsed = pairingProbeSchema.safeParse(await c.req.json().catch(() => null));
|
|
86
|
-
if (!parsed.success || !isDevicePairingSetupActive(parsed.data.pairingId))
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
if (!parsed.success || !isDevicePairingSetupActive(parsed.data.pairingId)) {
|
|
99
|
+
buckets.pairingExchange().fail(clientKey);
|
|
100
|
+
return c.json({
|
|
101
|
+
ok: false,
|
|
102
|
+
error: {
|
|
103
|
+
code: "PAIRING_NOT_FOUND",
|
|
104
|
+
message: "Pairing setup not found"
|
|
105
|
+
}
|
|
106
|
+
}, 404);
|
|
107
|
+
}
|
|
93
108
|
const identity = getOrCreateGatewayIdentity();
|
|
94
109
|
const payload = Buffer.from(JSON.stringify({
|
|
95
110
|
gatewayId: identity.id,
|
|
96
111
|
pairingId: parsed.data.pairingId,
|
|
97
112
|
issuedAt: Date.now()
|
|
98
113
|
})).toString("base64url");
|
|
114
|
+
buckets.pairingExchange().succeed(clientKey);
|
|
99
115
|
return c.json({
|
|
100
116
|
ok: true,
|
|
101
117
|
signedPayload: payload,
|
|
@@ -136,7 +152,7 @@ function registerDeviceRoutes(authenticated, deps) {
|
|
|
136
152
|
}, 409);
|
|
137
153
|
const setup = createDevicePairingSetup(routes, Date.now(), { targetKind: parsed.data.targetKind });
|
|
138
154
|
const identity = getOrCreateGatewayIdentity();
|
|
139
|
-
const encoded =
|
|
155
|
+
const encoded = pairingPayload({
|
|
140
156
|
version: 3,
|
|
141
157
|
pairingToken: setup.token,
|
|
142
158
|
gatewayId: identity.id,
|
|
@@ -146,11 +162,12 @@ function registerDeviceRoutes(authenticated, deps) {
|
|
|
146
162
|
targetKind: setup.targetKind,
|
|
147
163
|
expiresAt: setup.expiresAt
|
|
148
164
|
});
|
|
165
|
+
const invitation = setup.targetKind === "mobile" ? { universalLink: `https://link.xopc.ai/connect#p=${encoded}` } : { browserInvitation: formatBrowserPairingInvitation(encoded) };
|
|
149
166
|
return c.json({
|
|
150
167
|
ok: true,
|
|
151
168
|
setup: {
|
|
152
169
|
id: setup.id,
|
|
153
|
-
|
|
170
|
+
...invitation,
|
|
154
171
|
expiresAt: setup.expiresAt,
|
|
155
172
|
routes,
|
|
156
173
|
protocolVersion: 3,
|
|
@@ -136,7 +136,7 @@ export declare function replaceLatestSessionTurn(c: Context, deps: Authenticated
|
|
|
136
136
|
code: "TARGET_NOT_FOUND" | "NOT_LATEST" | "SESSION_BUSY" | "BAD_REQUEST" | "CONTEXT_UNAVAILABLE";
|
|
137
137
|
message: string;
|
|
138
138
|
};
|
|
139
|
-
},
|
|
139
|
+
}, 404 | 400 | 409, "json">) | (Response & import("hono").TypedResponse<{
|
|
140
140
|
ok: true;
|
|
141
141
|
payload: {
|
|
142
142
|
sessionKey: string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { browserPageContextsInputSchema } from "../../../../packages/gateway-contract/src/browser-page-context.js";
|
|
2
2
|
import { endpointTurnClaimSchema } from "../../../../packages/endpoint-tools-protocol/src/index.js";
|
|
3
3
|
import { init_providers, resolveModel } from "../../../providers/index.js";
|
|
4
|
+
import { validateWebchatAttachments, validateWebchatContent } from "../../chat-limits.js";
|
|
4
5
|
import { parseTurnContextRefs } from "../../../agent/source-context/types.js";
|
|
5
6
|
import { getModelThinking } from "../../../providers/model-thinking.js";
|
|
6
|
-
import { validateWebchatAttachments, validateWebchatContent } from "../../chat-limits.js";
|
|
7
7
|
import { withModelConfigLock } from "../../../session/model-config-lock.js";
|
|
8
8
|
import { browserPageContextToAgentContext } from "../../../agent/source-context/browser-page.js";
|
|
9
9
|
//#region src/gateway/hono/routes/session-input-handler.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { chooseModelThinking } from "../../../../packages/gateway-contract/src/model-selection.js";
|
|
2
2
|
import { init_providers, resolveModel } from "../../../providers/index.js";
|
|
3
|
-
import { getModelThinking } from "../../../providers/model-thinking.js";
|
|
4
3
|
import { validateWebchatAttachments, validateWebchatContent } from "../../chat-limits.js";
|
|
4
|
+
import { getModelThinking } from "../../../providers/model-thinking.js";
|
|
5
5
|
import { validateSideChatSelections } from "../../side-chat/context-snapshot.js";
|
|
6
6
|
import { SideChatError, parseThinkingLevel } from "../../side-chat/manager.js";
|
|
7
7
|
import "../../side-chat/index.js";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createLogger } from "../../../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../../../utils/logger.js";
|
|
1
3
|
import { init_public_url, validatePublicUrl } from "../../../config/public-url.js";
|
|
2
4
|
import { TUNNEL_REGISTRATION_SECRET_REQUIRED_CODE, TunnelRegistrationSecretError, getTunnelRegistrationSecretMeta, readTunnelRegistrationSecretFromConfigOnly, resolveTunnelBrokerUrl } from "../../../tunnel/env.js";
|
|
3
5
|
import { logTunnelAudit } from "../../../tunnel/tunnel-audit.js";
|
|
@@ -9,9 +11,11 @@ import { applyTunnelConsentToConfig, mergeTunnelConfigPatch, setTunnelEnabledInC
|
|
|
9
11
|
import { getGatewayPrincipal } from "../../security/gateway-principal.js";
|
|
10
12
|
import { consumeTunnelMutationLimit } from "../../../tunnel/tunnel-rate-limit.js";
|
|
11
13
|
import "../../../tunnel/index.js";
|
|
12
|
-
import { provisionTunnelRegistrationKey } from "../../../tunnel/xopc-cloud-registration.js";
|
|
14
|
+
import { TunnelRegistrationProvisionError, provisionTunnelRegistrationKey } from "../../../tunnel/xopc-cloud-registration.js";
|
|
13
15
|
//#region src/gateway/hono/routes/tunnel.ts
|
|
16
|
+
init_logger();
|
|
14
17
|
init_public_url();
|
|
18
|
+
const log = createLogger("Gateway:Tunnel");
|
|
15
19
|
async function configureTunnelFromService(deps, opts) {
|
|
16
20
|
await configureTunnelFromGatewayConfig(deps.service.currentConfig, opts);
|
|
17
21
|
}
|
|
@@ -161,6 +165,10 @@ function registerTunnelRoutes(authenticated, deps) {
|
|
|
161
165
|
});
|
|
162
166
|
authenticated.post("/api/tunnel/registration-key", tunnelMutationLimit, async (c) => {
|
|
163
167
|
const config = deps.service.currentConfig;
|
|
168
|
+
if (readTunnelRegistrationSecretFromConfigOnly(config)) return c.json({
|
|
169
|
+
ok: true,
|
|
170
|
+
reused: true
|
|
171
|
+
});
|
|
164
172
|
try {
|
|
165
173
|
const merged = mergeTunnelConfigPatch(config, { registrationSecret: await provisionTunnelRegistrationKey() });
|
|
166
174
|
if (merged.ok === false) return c.json({
|
|
@@ -176,9 +184,33 @@ function registerTunnelRoutes(authenticated, deps) {
|
|
|
176
184
|
return c.json({ ok: true });
|
|
177
185
|
} catch (error) {
|
|
178
186
|
const message = error instanceof Error ? error.message : String(error);
|
|
187
|
+
if (error instanceof TunnelRegistrationProvisionError) {
|
|
188
|
+
const status = error.code === "tunnel_key_limit_reached" ? 409 : error.code === "tunnel_oauth_required" ? 403 : 502;
|
|
189
|
+
log.warn({
|
|
190
|
+
err: error,
|
|
191
|
+
code: error.code,
|
|
192
|
+
upstreamStatus: error.status,
|
|
193
|
+
phase: "registration_key"
|
|
194
|
+
}, `Tunnel registration key failed: ${message}`);
|
|
195
|
+
return c.json({
|
|
196
|
+
ok: false,
|
|
197
|
+
error: {
|
|
198
|
+
code: error.code,
|
|
199
|
+
message
|
|
200
|
+
}
|
|
201
|
+
}, status);
|
|
202
|
+
}
|
|
203
|
+
log.warn({
|
|
204
|
+
err: error,
|
|
205
|
+
errorMessage: message,
|
|
206
|
+
phase: "registration_key"
|
|
207
|
+
}, `Tunnel registration key failed: ${message}`);
|
|
179
208
|
return c.json({
|
|
180
209
|
ok: false,
|
|
181
|
-
error:
|
|
210
|
+
error: {
|
|
211
|
+
code: "tunnel_registration_failed",
|
|
212
|
+
message
|
|
213
|
+
}
|
|
182
214
|
}, 502);
|
|
183
215
|
}
|
|
184
216
|
});
|
|
@@ -144,7 +144,8 @@ var BucketRegistry = class {
|
|
|
144
144
|
if (!this.pairingExchangeLimiter) this.pairingExchangeLimiter = new FailureLimiter({
|
|
145
145
|
maxFailures: 30,
|
|
146
146
|
windowMs: 5 * 6e4,
|
|
147
|
-
blockDurationMs: 5 * 6e4
|
|
147
|
+
blockDurationMs: 5 * 6e4,
|
|
148
|
+
burstCoalesceMs: 0
|
|
148
149
|
});
|
|
149
150
|
return this.pairingExchangeLimiter;
|
|
150
151
|
}
|
|
@@ -9,8 +9,8 @@ import { init_session_key, parseSessionKey } from "../../routing/session-key.js"
|
|
|
9
9
|
import { resolveExecutionContext } from "../../tasks/execution-context.js";
|
|
10
10
|
import { TaskRunCoordinator } from "../../tasks/task-run-coordinator.js";
|
|
11
11
|
import { formatAgentRunErrorForClient } from "../../agent/client-error-format.js";
|
|
12
|
-
import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
|
|
13
12
|
import "../chat-limits.js";
|
|
13
|
+
import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
|
|
14
14
|
import { ChatStreamMapper } from "../chat-stream/mapper.js";
|
|
15
15
|
import { resolveWebchatSessionKey } from "../resolve-webchat-session-key.js";
|
|
16
16
|
import { recordExplicitRelationshipFollowUp } from "../../user-context/relationship-continuity.js";
|