@linxin666/dsh-remote-web-ui 0.3.3 → 0.3.5

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 (37) hide show
  1. package/assets/mobile-service-worker.js +1 -2
  2. package/lib/client.js +95 -6
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +122 -8
  5. package/lib/mobile.js +2 -6
  6. package/lib/mobile.js.map +1 -1
  7. package/lib/types/client/FenceNotice.d.ts +12 -9
  8. package/lib/types/client/FenceNotice.d.ts.map +1 -1
  9. package/lib/types/client/FenceNotice.js +67 -2
  10. package/lib/types/client/PluginSettingsCard.d.ts +2 -0
  11. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  12. package/lib/types/client/PluginSettingsCard.js +2 -2
  13. package/lib/types/client/locales.d.ts +12 -0
  14. package/lib/types/client/locales.d.ts.map +1 -1
  15. package/lib/types/client/locales.js +12 -0
  16. package/lib/types/dsh-home.d.ts.map +1 -1
  17. package/lib/types/dsh-home.js +9 -3
  18. package/lib/types/index.d.ts.map +1 -1
  19. package/lib/types/index.js +24 -2
  20. package/lib/types/mobile/api.d.ts.map +1 -1
  21. package/lib/types/mobile/api.js +2 -3
  22. package/lib/types/mobile-api.d.ts +14 -0
  23. package/lib/types/mobile-api.d.ts.map +1 -1
  24. package/lib/types/mobile-api.js +53 -1
  25. package/lib/types/mobile-routes.d.ts.map +1 -1
  26. package/lib/types/mobile-routes.js +25 -2
  27. package/package.json +3 -1
  28. package/src/client/FenceNotice.tsx +81 -1
  29. package/src/client/PluginSettingsCard.tsx +4 -2
  30. package/src/client/locales.ts +12 -0
  31. package/src/client/remote.module.css +72 -4
  32. package/src/client/settings-card.module.css +5 -0
  33. package/src/dsh-home.ts +9 -3
  34. package/src/index.ts +26 -2
  35. package/src/mobile/api.ts +2 -3
  36. package/src/mobile-api.ts +59 -0
  37. package/src/mobile-routes.ts +24 -2
package/lib/index.js CHANGED
@@ -6,9 +6,11 @@ import z from "schemastery";
6
6
  import { randomBytes } from "node:crypto";
7
7
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
8
8
  import { homedir, networkInterfaces } from "node:os";
9
+ import { isAbsolute as isAbsolute$1, join as join$1 } from "node:path/posix";
9
10
  import { z as z$1 } from "zod";
10
11
  import { readFile } from "node:fs/promises";
11
12
  import { fileURLToPath } from "node:url";
13
+ import { gzipSync } from "node:zlib";
12
14
  import { RpcId } from "@deepseek-ai/dsh-host-apiproxy/api/rpc";
13
15
  import http, { request } from "node:http";
14
16
  import { connect } from "node:net";
@@ -650,8 +652,9 @@ function sanitizeUserAgent(raw) {
650
652
  */
651
653
  /** Expand a leading ~ (or ~user) in a path, platform-style. */
652
654
  function expandHome(path, home = homedir()) {
655
+ const j = home.startsWith("/") ? join$1 : join;
653
656
  if (path === "~") return home;
654
- if (path.startsWith("~/") || path.startsWith("~\\")) return join(home, path.slice(2));
657
+ if (path.startsWith("~/") || path.startsWith("~\\")) return j(home, path.slice(2));
655
658
  return path;
656
659
  }
657
660
  /**
@@ -661,12 +664,15 @@ function expandHome(path, home = homedir()) {
661
664
  * @returns the absolute DSH home path.
662
665
  */
663
666
  function resolveDshHome(env = process.env, home = homedir()) {
667
+ const isPosix = home.startsWith("/");
668
+ const j = isPosix ? join$1 : join;
669
+ const isAbs = isPosix ? isAbsolute$1 : isAbsolute;
664
670
  const raw = env.DSH_HOME;
665
671
  if (raw !== void 0 && raw.trim() !== "") {
666
672
  const expanded = expandHome(raw.trim(), home);
667
- return isAbsolute(expanded) ? expanded : join(process.cwd(), expanded);
673
+ return isAbs(expanded) ? expanded : j(process.cwd(), expanded);
668
674
  }
669
- return join(home, ".dsh");
675
+ return j(home, ".dsh");
670
676
  }
671
677
  /** Resolve the DSH home directory from the live environment. */
672
678
  function dshHome() {
@@ -1442,7 +1448,8 @@ function makeMobileRoutes() {
1442
1448
  writeStatic(res, 200, "text/html", offlineHtml());
1443
1449
  };
1444
1450
  let bundleBody;
1445
- const handleBundle = async (_req, res) => {
1451
+ let bundleGzip;
1452
+ const handleBundle = async (req, res) => {
1446
1453
  if (bundleBody === void 0) {
1447
1454
  const path = mobileBundlePath();
1448
1455
  if (!existsSync(path)) {
@@ -1451,12 +1458,31 @@ function makeMobileRoutes() {
1451
1458
  }
1452
1459
  try {
1453
1460
  bundleBody = await readFile(path, "utf8");
1461
+ bundleGzip = gzipSync(Buffer.from(bundleBody, "utf8"));
1454
1462
  } catch {
1455
1463
  writeStatic(res, 500, "text/plain", "failed to read the mobile bundle");
1456
1464
  return;
1457
1465
  }
1458
1466
  }
1459
- writeStatic(res, 200, "text/javascript", bundleBody);
1467
+ const acceptEncoding = req.headers["accept-encoding"] ?? "";
1468
+ if (typeof acceptEncoding === "string" && acceptEncoding.includes("gzip") && bundleGzip !== void 0) {
1469
+ res.writeHead(200, {
1470
+ "content-type": "text/javascript; charset=utf-8",
1471
+ "content-encoding": "gzip",
1472
+ "cache-control": "no-cache",
1473
+ vary: "Accept-Encoding",
1474
+ "referrer-policy": "no-referrer"
1475
+ });
1476
+ res.end(bundleGzip);
1477
+ } else {
1478
+ res.writeHead(200, {
1479
+ "content-type": "text/javascript; charset=utf-8",
1480
+ "cache-control": "no-cache",
1481
+ vary: "Accept-Encoding",
1482
+ "referrer-policy": "no-referrer"
1483
+ });
1484
+ res.end(bundleBody);
1485
+ }
1460
1486
  };
1461
1487
  let workerBody;
1462
1488
  const handleWorker = async (_req, res) => {
@@ -1568,6 +1594,8 @@ const MOBILE_ALLOWLIST = /* @__PURE__ */ new Set([
1568
1594
  const MOBILE_PREFERENCES_METHOD = "mobile.preferences";
1569
1595
  const MOBILE_PENDING_METHOD = "mobile.pending";
1570
1596
  const MOBILE_RESPOND_METHOD = "mobile.respond";
1597
+ /** Execute a slash command through the host command registry (#1125). */
1598
+ const MOBILE_COMMAND_METHOD = "mobile.command";
1571
1599
  /** One session.list page (thin phones load incrementally). */
1572
1600
  const SESSION_PAGE_SIZE = 20;
1573
1601
  /** SSE keep-alive ping cadence for the live mux stream (single connection). */
@@ -1650,7 +1678,7 @@ function makeMobileApiRoutes(deps) {
1650
1678
  return;
1651
1679
  }
1652
1680
  const method = pathname.slice(MOBILE_API_METHOD_PREFIX.length);
1653
- const local = method === MOBILE_PREFERENCES_METHOD || method === MOBILE_PENDING_METHOD || method === MOBILE_RESPOND_METHOD;
1681
+ const local = method === MOBILE_PREFERENCES_METHOD || method === MOBILE_PENDING_METHOD || method === MOBILE_RESPOND_METHOD || method === MOBILE_COMMAND_METHOD;
1654
1682
  if (!MOBILE_ALLOWLIST.has(method) && !local) {
1655
1683
  writeJson(res, 403, {
1656
1684
  ok: false,
@@ -1736,6 +1764,73 @@ function makeMobileApiRoutes(deps) {
1736
1764
  }
1737
1765
  });
1738
1766
  }
1767
+ } else if (method === MOBILE_COMMAND_METHOD) {
1768
+ const payload = parsed.payload;
1769
+ const sessionId = payload?.sessionId;
1770
+ const line = payload?.line;
1771
+ if (typeof sessionId !== "string" || typeof line !== "string") writeJson(res, 200, {
1772
+ type: "server-response",
1773
+ rpcId,
1774
+ result: {
1775
+ ok: false,
1776
+ error: {
1777
+ code: "bad-request",
1778
+ message: "missing sessionId or line"
1779
+ }
1780
+ }
1781
+ });
1782
+ else if (deps.commandDispatcher === void 0) writeJson(res, 200, {
1783
+ type: "server-response",
1784
+ rpcId,
1785
+ result: {
1786
+ ok: false,
1787
+ error: {
1788
+ code: "unavailable",
1789
+ message: "command dispatcher is not available"
1790
+ }
1791
+ }
1792
+ });
1793
+ else try {
1794
+ const abort = new AbortController();
1795
+ res.on("close", () => {
1796
+ if (!res.writableEnded) abort.abort();
1797
+ });
1798
+ const outcome = await deps.commandDispatcher.execute(sessionId, line, abort.signal);
1799
+ if ("error" in outcome) writeJson(res, 200, {
1800
+ type: "server-response",
1801
+ rpcId,
1802
+ result: {
1803
+ ok: false,
1804
+ error: {
1805
+ code: outcome.error,
1806
+ message: outcome.error
1807
+ }
1808
+ }
1809
+ });
1810
+ else writeJson(res, 200, {
1811
+ type: "server-response",
1812
+ rpcId,
1813
+ result: {
1814
+ ok: true,
1815
+ value: {
1816
+ matched: true,
1817
+ result: outcome.result
1818
+ }
1819
+ }
1820
+ });
1821
+ } catch (error) {
1822
+ writeJson(res, 200, {
1823
+ type: "server-response",
1824
+ rpcId,
1825
+ result: {
1826
+ ok: false,
1827
+ error: {
1828
+ code: "internal",
1829
+ message: error instanceof Error ? error.message : String(error)
1830
+ }
1831
+ }
1832
+ });
1833
+ }
1739
1834
  }
1740
1835
  return;
1741
1836
  }
@@ -3712,7 +3807,12 @@ const UUID_POLYFILL_SCRIPT = [
3712
3807
  /** Stable cordis plugin name. */
3713
3808
  const name = "remote-web-ui";
3714
3809
  /** Services required before the pairing surfaces can mount. */
3715
- const inject = ["webServer", "apiProxy"];
3810
+ const inject = [
3811
+ "webServer",
3812
+ "apiProxy",
3813
+ "commands",
3814
+ "agents"
3815
+ ];
3716
3816
  /**
3717
3817
  * Settings namespace of the remote-control capability — the section the web
3718
3818
  * settings surface edits. Spelled here rather than imported: the browser
@@ -3917,7 +4017,17 @@ function applyImpl(ctx, config) {
3917
4017
  service,
3918
4018
  apiProxy,
3919
4019
  pendingTracker: new PendingTracker(),
3920
- mobileEnterToSend: () => resolve().mobileEnterToSend
4020
+ mobileEnterToSend: () => resolve().mobileEnterToSend,
4021
+ commandDispatcher: ctx.commands !== void 0 && ctx.agents !== void 0 ? { async execute(sessionId, line, signal) {
4022
+ const agent = ctx.agents.get(sessionId);
4023
+ if (agent === void 0) return { error: "session-not-found" };
4024
+ const execution = await ctx.commands.execute(agent, line, [], signal);
4025
+ if (execution === void 0) return { error: "unknown-command" };
4026
+ return {
4027
+ ok: true,
4028
+ result: execution.result
4029
+ };
4030
+ } } : void 0
3921
4031
  }) : [],
3922
4032
  ...apiProxy !== void 0 ? makePairedModelCatalogRoutes({
3923
4033
  service,
@@ -3977,6 +4087,10 @@ function applyImpl(ctx, config) {
3977
4087
  if (!resolve().enabled) return false;
3978
4088
  return isPairedDeviceRequest(service, request);
3979
4089
  });
4090
+ if (lanAddresses.length > 0) {
4091
+ const urls = lanAddresses.map((ip) => `http://${ip}:${String(ctx.webServer.port)}/m/`).join(" , ");
4092
+ console.log(`remote-web-ui: mobile UI reachable on LAN at ${urls}`);
4093
+ }
3980
4094
  const sync = () => {
3981
4095
  const value = resolve();
3982
4096
  service.config = pairingConfigOf(value);
package/lib/mobile.js CHANGED
@@ -7610,13 +7610,9 @@ async function prompt(sessionId, text) {
7610
7610
  }
7611
7611
  /** Send one slash command line (e.g. `/permission workspace-write`). */
7612
7612
  async function sendCommand(sessionId, line) {
7613
- return await callUnary("session.prompt", {
7613
+ return await callUnary("mobile.command", {
7614
7614
  sessionId,
7615
- mode: "queue",
7616
- content: [{
7617
- type: "text",
7618
- text: line
7619
- }]
7615
+ line
7620
7616
  });
7621
7617
  }
7622
7618
  /**