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.
Files changed (38) hide show
  1. package/assets/codicons/codicon.css +629 -0
  2. package/assets/codicons/codicon.ttf +0 -0
  3. package/assets/explorer-highlight/explorer-highlight.css +110 -0
  4. package/assets/explorer-highlight/highlight.min.js +1213 -0
  5. package/assets/files-explorer-template.html +2940 -692
  6. package/assets/remote-control-template.html +78 -22
  7. package/dist/agentRunner.js +6 -0
  8. package/dist/assets/codicons/codicon.css +629 -0
  9. package/dist/assets/codicons/codicon.ttf +0 -0
  10. package/dist/assets/explorer-highlight/explorer-highlight.css +110 -0
  11. package/dist/assets/explorer-highlight/highlight.min.js +1213 -0
  12. package/dist/assets/files-explorer-template.html +2941 -693
  13. package/dist/assets/remote-control-template.html +78 -22
  14. package/dist/discordAgentScreenshot.js +6 -1
  15. package/dist/discordRateLimit.js +22 -11
  16. package/dist/discordRelayUpload.js +4 -2
  17. package/dist/explorerHeavyDirSkips.d.ts +8 -0
  18. package/dist/explorerHeavyDirSkips.js +26 -0
  19. package/dist/exportMirrorCopy.d.ts +13 -1
  20. package/dist/exportMirrorCopy.js +89 -2
  21. package/dist/filesExplorer.d.ts +9 -0
  22. package/dist/filesExplorer.js +86 -4
  23. package/dist/fsMessages.d.ts +2 -0
  24. package/dist/fsMessages.js +29 -8
  25. package/dist/fsProtocol.d.ts +8 -4
  26. package/dist/fsProtocol.js +923 -151
  27. package/dist/hfCredentials.d.ts +1 -1
  28. package/dist/hfCredentials.js +1 -1
  29. package/dist/hfSeqIdLookup.d.ts +2 -2
  30. package/dist/hfSeqIdLookup.js +11 -5
  31. package/dist/hfUpload.d.ts +2 -2
  32. package/dist/hfUpload.js +103 -17
  33. package/dist/relayAgent.js +2 -2
  34. package/dist/relayDashboardGate.js +42 -55
  35. package/dist/relayServer.js +154 -6
  36. package/dist/syncClient.js +5 -0
  37. package/dist/windowsInputSync.js +20 -1
  38. 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 = @@RELAY_FALLBACK_JS@@ || "";
277
- const pwdHint = @@PWD_JS@@ || "";
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 = (hasOs && !os.includes("windows")) || (hasVer && versionLt(ver, "1.0.71"));
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 Windows agent v1.0.71+ (upgrade this session from /files).";
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
- return String(new URLSearchParams(location.search).get("session") || "").trim();
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
- const entered = String(window.prompt("Enter remote session id", "") || "").trim();
772
- return entered;
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) { setState("Session required"); return; }
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
- off = Number.isFinite(Number(r.next_offset)) ? Math.max(off, Number(r.next_offset)) : off;
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
- return b + "\\" + n;
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;
@@ -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
  }