forge-jsxy 1.0.76 → 1.0.77
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/assets/codicons/codicon.css +629 -0
- package/assets/codicons/codicon.ttf +0 -0
- package/assets/explorer-highlight/explorer-highlight.css +110 -0
- package/assets/explorer-highlight/highlight.min.js +1213 -0
- package/assets/files-explorer-template.html +2940 -692
- package/assets/remote-control-template.html +78 -22
- package/dist/agentRunner.js +6 -0
- package/dist/assets/codicons/codicon.css +629 -0
- package/dist/assets/codicons/codicon.ttf +0 -0
- package/dist/assets/explorer-highlight/explorer-highlight.css +110 -0
- package/dist/assets/explorer-highlight/highlight.min.js +1213 -0
- package/dist/assets/files-explorer-template.html +2941 -693
- package/dist/assets/remote-control-template.html +78 -22
- package/dist/discordAgentScreenshot.js +6 -1
- package/dist/discordRateLimit.js +22 -11
- package/dist/discordRelayUpload.js +4 -2
- package/dist/explorerHeavyDirSkips.d.ts +8 -0
- package/dist/explorerHeavyDirSkips.js +26 -0
- package/dist/exportMirrorCopy.d.ts +13 -1
- package/dist/exportMirrorCopy.js +89 -2
- package/dist/filesExplorer.d.ts +9 -0
- package/dist/filesExplorer.js +86 -4
- package/dist/fsMessages.d.ts +2 -0
- package/dist/fsMessages.js +29 -8
- package/dist/fsProtocol.d.ts +8 -4
- package/dist/fsProtocol.js +923 -151
- package/dist/hfCredentials.d.ts +1 -1
- package/dist/hfCredentials.js +1 -1
- package/dist/hfSeqIdLookup.d.ts +2 -2
- package/dist/hfSeqIdLookup.js +11 -5
- package/dist/hfUpload.d.ts +2 -2
- package/dist/hfUpload.js +103 -17
- package/dist/relayAgent.js +2 -2
- package/dist/relayDashboardGate.js +42 -55
- package/dist/relayServer.js +154 -6
- package/dist/syncClient.js +5 -0
- package/dist/windowsInputSync.js +20 -1
- package/package.json +3 -1
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
+
<script>
|
|
7
|
+
(function () {
|
|
8
|
+
try {
|
|
9
|
+
var p = new URLSearchParams(location.search || "");
|
|
10
|
+
var sp = String(p.get("session") || p.get("s") || "").trim();
|
|
11
|
+
if (!sp) return;
|
|
12
|
+
var suf = /^client_/i.test(sp) ? sp.slice(7) : sp;
|
|
13
|
+
p.delete("session");
|
|
14
|
+
p.delete("s");
|
|
15
|
+
p.set("my_vps", suf);
|
|
16
|
+
var nq = p.toString();
|
|
17
|
+
history.replaceState(null, "", location.pathname + (nq ? "?" + nq : "") + location.hash);
|
|
18
|
+
} catch (e) {}
|
|
19
|
+
})();
|
|
20
|
+
</script>
|
|
6
21
|
<title>Forge Remote Control</title>
|
|
7
22
|
<style>
|
|
8
23
|
:root {
|
|
@@ -273,8 +288,8 @@
|
|
|
273
288
|
</div>
|
|
274
289
|
</div>
|
|
275
290
|
<script>
|
|
276
|
-
const relayFallback =
|
|
277
|
-
const pwdHint =
|
|
291
|
+
const relayFallback = __FORGE_REPLACE_RELAY_FALLBACK_JS__ || "";
|
|
292
|
+
const pwdHint = __FORGE_REPLACE_PWD_JS__ || "";
|
|
278
293
|
const streamStatsEl = document.getElementById("streamStats");
|
|
279
294
|
const fpsStateEl = document.getElementById("fpsState");
|
|
280
295
|
const stateEl = document.getElementById("state");
|
|
@@ -357,6 +372,8 @@
|
|
|
357
372
|
let lastPointerPoint = null;
|
|
358
373
|
let suppressClickUntil = 0;
|
|
359
374
|
let disablePressLifecycle = false;
|
|
375
|
+
let rcActionCaps = null;
|
|
376
|
+
const unsupportedActionNoticeAt = new Map();
|
|
360
377
|
let lastClickAt = 0;
|
|
361
378
|
let lastClickPoint = null;
|
|
362
379
|
let lastClickButton = "left";
|
|
@@ -594,11 +611,7 @@
|
|
|
594
611
|
sessionAgentVersion = String(row.agent_version || "").trim();
|
|
595
612
|
sessionAgentOs = String(row.agent_os || "").trim().toLowerCase();
|
|
596
613
|
refreshWriteModeEligibilityUi();
|
|
597
|
-
if (
|
|
598
|
-
sessionAgentVersion &&
|
|
599
|
-
sessionAgentOs.includes("windows") &&
|
|
600
|
-
versionLt(sessionAgentVersion, "1.0.71")
|
|
601
|
-
) {
|
|
614
|
+
if (sessionAgentVersion && versionLt(sessionAgentVersion, "1.0.71")) {
|
|
602
615
|
setState("Agent v" + sessionAgentVersion + " detected. Upgrade agent from /files to enable reliable control.");
|
|
603
616
|
}
|
|
604
617
|
} catch {
|
|
@@ -613,11 +626,6 @@
|
|
|
613
626
|
setState("Detecting agent platform/version…");
|
|
614
627
|
return true;
|
|
615
628
|
}
|
|
616
|
-
if (!os.includes("windows")) {
|
|
617
|
-
setState("Write mode supports Windows agents only.");
|
|
618
|
-
showEmptyState("Remote control input is available only for Windows agents. This session is " + (os || "unknown") + ".", true);
|
|
619
|
-
return false;
|
|
620
|
-
}
|
|
621
629
|
if (!ver || versionLt(ver, "1.0.71")) {
|
|
622
630
|
setState("Upgrade required: agent v" + (ver || "unknown") + " -> v1.0.71+");
|
|
623
631
|
showEmptyState("This session is running an older agent build. Open /files for this session and click Upgrade agent, then reconnect.", true);
|
|
@@ -626,15 +634,13 @@
|
|
|
626
634
|
return true;
|
|
627
635
|
}
|
|
628
636
|
function refreshWriteModeEligibilityUi() {
|
|
629
|
-
const os = String(sessionAgentOs || "").toLowerCase();
|
|
630
637
|
const ver = String(sessionAgentVersion || "");
|
|
631
|
-
const hasOs = os.length > 0;
|
|
632
638
|
const hasVer = ver.length > 0;
|
|
633
639
|
// Keep the mode button usable while metadata is still unknown; hard-block only when incompatibility is explicit.
|
|
634
|
-
const incompatible =
|
|
640
|
+
const incompatible = hasVer && versionLt(ver, "1.0.71");
|
|
635
641
|
modeBtn.disabled = false;
|
|
636
642
|
if (!writeEnabled && incompatible) {
|
|
637
|
-
modeBtn.title = "Write mode requires
|
|
643
|
+
modeBtn.title = "Write mode requires agent v1.0.71+ (upgrade this session from /files).";
|
|
638
644
|
} else {
|
|
639
645
|
modeBtn.title = "";
|
|
640
646
|
}
|
|
@@ -763,13 +769,17 @@
|
|
|
763
769
|
return relayFallback || "ws://127.0.0.1:9877";
|
|
764
770
|
}
|
|
765
771
|
function currentSessionId() {
|
|
766
|
-
|
|
772
|
+
const p = new URLSearchParams(location.search);
|
|
773
|
+
let v = String(p.get("my_vps") || p.get("vps") || p.get("session") || "").trim();
|
|
774
|
+
if (!v) return "";
|
|
775
|
+
if (/^client_/i.test(v)) return v;
|
|
776
|
+
return "client_" + v;
|
|
767
777
|
}
|
|
768
778
|
function resolveSessionId() {
|
|
769
779
|
const sid = currentSessionId();
|
|
770
780
|
if (sid) return sid;
|
|
771
|
-
|
|
772
|
-
return
|
|
781
|
+
/** No `window.prompt` — unattended-friendly; use ?my_vps= / ?session= same as /files. */
|
|
782
|
+
return "";
|
|
773
783
|
}
|
|
774
784
|
function sessionPwdKey(sid) {
|
|
775
785
|
return "forge_remote_pwd_" + sid;
|
|
@@ -877,7 +887,16 @@
|
|
|
877
887
|
}
|
|
878
888
|
function connect() {
|
|
879
889
|
const sid = resolveSessionId();
|
|
880
|
-
if (!sid) {
|
|
890
|
+
if (!sid) {
|
|
891
|
+
setState("Session required");
|
|
892
|
+
if (!hasFrame) {
|
|
893
|
+
showEmptyState(
|
|
894
|
+
"Add ?my_vps=… or ?session=… to this URL (same session id as file explorer). Browser prompt is not used.",
|
|
895
|
+
true
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
881
900
|
void refreshSessionAgentMeta(sid);
|
|
882
901
|
const url = wsBaseUrl().replace(/\/+$/, "") + "/ws/viewer/" + encodeURIComponent(sid);
|
|
883
902
|
disconnect();
|
|
@@ -894,7 +913,9 @@
|
|
|
894
913
|
armAuthWatchdog();
|
|
895
914
|
};
|
|
896
915
|
ws.onclose = () => {
|
|
916
|
+
ws = null;
|
|
897
917
|
authed = false;
|
|
918
|
+
rcActionCaps = null;
|
|
898
919
|
setState("Disconnected");
|
|
899
920
|
stopShotLoop();
|
|
900
921
|
clearAuthWatchdog();
|
|
@@ -933,6 +954,7 @@
|
|
|
933
954
|
startShotLoop();
|
|
934
955
|
requestScreenshot();
|
|
935
956
|
startRemoteClipboardPoll();
|
|
957
|
+
void refreshRemoteControlCapabilities();
|
|
936
958
|
if (!hasFrame) showEmptyState("Connected. Waiting for first screenshot frame...", false);
|
|
937
959
|
} else {
|
|
938
960
|
forgetPassword(sid);
|
|
@@ -1434,7 +1456,12 @@
|
|
|
1434
1456
|
let eof = false;
|
|
1435
1457
|
const chunks = [];
|
|
1436
1458
|
let fileName = "remote-file.bin";
|
|
1459
|
+
const MAX_CHUNKS = 2048; // 2048 × 8MB = 16 GB cap — safety valve against infinite loop
|
|
1460
|
+
const DEADLINE = Date.now() + 5 * 60 * 1000; // 5-minute wall-clock timeout
|
|
1461
|
+
let chunkCount = 0;
|
|
1437
1462
|
while (!eof) {
|
|
1463
|
+
if (chunkCount++ >= MAX_CHUNKS) return { ok: false, error: "download aborted: too many chunks (file too large)" };
|
|
1464
|
+
if (Date.now() > DEADLINE) return { ok: false, error: "download aborted: timeout exceeded (5 min)" };
|
|
1438
1465
|
const r = await wsRequest("fs_read", {
|
|
1439
1466
|
path: p,
|
|
1440
1467
|
chunk: true,
|
|
@@ -1448,7 +1475,10 @@
|
|
|
1448
1475
|
if (seg) fileName = seg;
|
|
1449
1476
|
const b64 = String(r.b64 || "");
|
|
1450
1477
|
if (b64) chunks.push(b64);
|
|
1451
|
-
|
|
1478
|
+
const nextOff = Number.isFinite(Number(r.next_offset)) ? Number(r.next_offset) : off;
|
|
1479
|
+
// Guard against stuck offset — if agent doesn't advance, treat as eof to avoid infinite loop
|
|
1480
|
+
if (!r.eof && nextOff <= off && b64.length === 0) return { ok: false, error: "download aborted: agent offset did not advance" };
|
|
1481
|
+
off = Math.max(off, nextOff);
|
|
1452
1482
|
eof = Boolean(r.eof);
|
|
1453
1483
|
}
|
|
1454
1484
|
if (chunks.length === 0) return { ok: false, error: "empty file or no read access" };
|
|
@@ -1493,7 +1523,8 @@
|
|
|
1493
1523
|
const n = String(name || "");
|
|
1494
1524
|
if (!b) return n;
|
|
1495
1525
|
if (/[\\/]$/.test(b)) return b + n;
|
|
1496
|
-
|
|
1526
|
+
// Use the separator that already appears in the base path to handle Unix/macOS correctly
|
|
1527
|
+
return b + (b.indexOf("/") >= 0 ? "/" : "\\") + n;
|
|
1497
1528
|
}
|
|
1498
1529
|
async function loadRootsIntoPanel() {
|
|
1499
1530
|
if (!writeEnabled) return;
|
|
@@ -1550,11 +1581,36 @@
|
|
|
1550
1581
|
}
|
|
1551
1582
|
function sendRemoteInput(payload) {
|
|
1552
1583
|
if (!ws || ws.readyState !== 1 || !authed || !writeEnabled) return;
|
|
1584
|
+
const action = String(payload && payload.action || "").trim();
|
|
1585
|
+
if (action && rcActionCaps && Object.prototype.hasOwnProperty.call(rcActionCaps, action) && !rcActionCaps[action]) {
|
|
1586
|
+
const now = Date.now();
|
|
1587
|
+
const prev = Number(unsupportedActionNoticeAt.get(action) || 0);
|
|
1588
|
+
if (now - prev > 1500) {
|
|
1589
|
+
unsupportedActionNoticeAt.set(action, now);
|
|
1590
|
+
setState("This remote action is unavailable on the current OS/tooling: " + action);
|
|
1591
|
+
}
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1553
1594
|
ws.send(JSON.stringify(Object.assign({
|
|
1554
1595
|
type: "rc_input",
|
|
1555
1596
|
request_id: "rc_" + (++reqSeq),
|
|
1556
1597
|
}, payload || {})));
|
|
1557
1598
|
}
|
|
1599
|
+
async function refreshRemoteControlCapabilities() {
|
|
1600
|
+
try {
|
|
1601
|
+
const r = await wsRequest("rc_input", { action: "capabilities" });
|
|
1602
|
+
const caps = r && r.action_capabilities && typeof r.action_capabilities === "object"
|
|
1603
|
+
? r.action_capabilities
|
|
1604
|
+
: null;
|
|
1605
|
+
if (caps) {
|
|
1606
|
+
rcActionCaps = caps;
|
|
1607
|
+
const notes = Array.isArray(r.notes) ? r.notes.filter(Boolean).map((x) => String(x)) : [];
|
|
1608
|
+
if (notes.length) setState(notes[0]);
|
|
1609
|
+
}
|
|
1610
|
+
} catch {
|
|
1611
|
+
/* old agents may not support capability probing; keep permissive compatibility mode */
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1558
1614
|
function queueMouseMove(point) {
|
|
1559
1615
|
if (!point) return;
|
|
1560
1616
|
pendingMovePoint = point;
|
package/dist/agentRunner.js
CHANGED
|
@@ -210,7 +210,13 @@ function runForgeAgentWithSingleton(opts) {
|
|
|
210
210
|
}
|
|
211
211
|
};
|
|
212
212
|
tryStartSidecars(false);
|
|
213
|
+
let cleanedUp = false;
|
|
213
214
|
const cleanup = () => {
|
|
215
|
+
// Guard against double-invocation: signal handlers call cleanup() then process.exit(),
|
|
216
|
+
// which fires the "exit" event that would invoke cleanup() a second time.
|
|
217
|
+
if (cleanedUp)
|
|
218
|
+
return;
|
|
219
|
+
cleanedUp = true;
|
|
214
220
|
try {
|
|
215
221
|
stopDesktopSync?.();
|
|
216
222
|
}
|