dsh-grok-subscription 2.0.7 → 2.0.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.8 — 2026-10-12
4
+
5
+ - Fix **sign-in reporting only `Grok subscription RPC "login/cli" timed out after 20000ms`** when DSH cannot reach `auth.x.ai`. Starting sign-in makes two requests in a row, OIDC discovery and then the device-code request, and each had its own 20s timeout under a 20s RPC ceiling. On a network that silently drops the connection, undici gave up on each TCP connect after about 10s; discovery then fell back to the same host, and the RPC ceiling won the race at 20s, so the real cause never reached the panel. Fixes #2.
6
+ - Both requests now share one 15s deadline, which ends before the RPC ceiling even with the status read that follows. A request that gets no HTTP answer is reported as `Cannot reach auth.x.ai (…)`, naming the kind of failure (connection timed out, connection refused, DNS lookup failed, TLS certificate rejected, timed out) without echoing an address or proxy URL. Discovery no longer falls back to the same unreachable host; a bad or mismatched discovery document still falls back to the documented endpoints.
7
+ - When sign-in cannot reach `auth.x.ai`, Settings explains the usual cause under the error: DSH does not read the macOS or Windows system proxy setting, so a browser that opens x.ai does not mean DSH can. Set `https_proxy=http://127.0.0.1:<port>` (an HTTP or mixed port; `socks5://` is not supported) in `~/.dsh/.env` — `%USERPROFILE%\.dsh\.env` on Windows — and restart DSH completely, or use the proxy app's TUN mode. Both guides have a matching troubleshooting row. The unused "no sign-in link" warning path left over from the CLI-based sign-in is removed.
8
+ - No model-routing, image-input, quota, or dependency changes. Update the plugin and fully restart DSH.
9
+
3
10
  ## 2.0.7 — 2026-10-09
4
11
 
5
12
  - Give the installed plugin card a localized display name: **Grok Subscription** in English and **Grok 订阅** in Simplified Chinese. Export and ship package-level `locale/en.json` and `locale/zh-CN.json` with `meta.title` and `meta.description`, the same metadata contract used by DSH. The npm package name remains `dsh-grok-subscription`.
package/README.en.md CHANGED
@@ -61,13 +61,13 @@ Pick a Grok model your account can use, adjust reasoning effort as needed, and s
61
61
  <details>
62
62
  <summary>Check the version / pin one / fall back when the UI install fails</summary>
63
63
 
64
- **Settings → Plugins** lists the installed plugins and their versions, and the plugin's own **Settings → Grok Subscription** page shows a version card. To pin a version, put the full `package@version` in the install field, for example `dsh-grok-subscription@2.0.7`.
64
+ **Settings → Plugins** lists the installed plugins and their versions, and the plugin's own **Settings → Grok Subscription** page shows a version card. To pin a version, put the full `package@version` in the install field, for example `dsh-grok-subscription@2.0.8`.
65
65
 
66
66
  If the UI install fails (an unusual profile directory, say), the equivalent runs against the desktop profile itself (pnpm ≥ 11):
67
67
 
68
68
  ```sh
69
69
  cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
70
- pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.7
70
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.8
71
71
  ```
72
72
 
73
73
  `dsh plugin --profile desktop …` is refused: the Electron application owns that profile exclusively. Sign-in and troubleshooting are in the [full user guide](docs/guide.en.md).
package/README.md CHANGED
@@ -61,13 +61,13 @@
61
61
  <details>
62
62
  <summary>确认版本 / 固定版本 / 界面安装失败时的兜底</summary>
63
63
 
64
- **设置 → 插件** 会列出已安装插件与版本,插件自己的 **设置 → Grok 订阅** 页也有版本卡片。要固定版本,在安装框里填入完整的 `包名@版本`,例如 `dsh-grok-subscription@2.0.7`。
64
+ **设置 → 插件** 会列出已安装插件与版本,插件自己的 **设置 → Grok 订阅** 页也有版本卡片。要固定版本,在安装框里填入完整的 `包名@版本`,例如 `dsh-grok-subscription@2.0.8`。
65
65
 
66
66
  界面安装失败时(例如 profile 目录权限异常),可以对桌面版 profile 手动执行等价操作(pnpm ≥ 11):
67
67
 
68
68
  ```sh
69
69
  cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
70
- pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.7
70
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.8
71
71
  ```
72
72
 
73
73
  `dsh plugin --profile desktop …` 会被拒绝:桌面版 profile 由 Electron 应用独占。登录方式与故障排查见 [完整使用指南](docs/guide.zh-CN.md)。
package/docs/guide.en.md CHANGED
@@ -8,7 +8,7 @@ For quick setup, see the [README](../README.en.md#install). This guide covers de
8
8
 
9
9
  You need the DeepSeek Harness desktop app and an account with Grok Build access. The grok command is not required. The plugin declares Node.js `^22.19.0 || >=24.0.0`; DSH peers use `>=0.1.5-rc.2 <0.3.0-0`, which spans the whole 0.1 and 0.2 lines. See [package.json](../package.json) for the full requirements. Declared compatibility is not a claim that every version has passed live verification.
10
10
 
11
- **Install:** in the desktop app open **Settings → Plugins**, type the package name `dsh-grok-subscription` into the install field, and install it. Then **quit and restart the desktop app completely**; refreshing the page alone does not reload the Host adapter. To pin a version, put the full `package@version` in that field, for example `dsh-grok-subscription@2.0.7`.
11
+ **Install:** in the desktop app open **Settings → Plugins**, type the package name `dsh-grok-subscription` into the install field, and install it. Then **quit and restart the desktop app completely**; refreshing the page alone does not reload the Host adapter. To pin a version, put the full `package@version` in that field, for example `dsh-grok-subscription@2.0.8`.
12
12
 
13
13
  **The plugin depends on the host's own pi-ai adapter.** From 2.0.0 the `grok-build` route is served entirely by the host's `@deepseek-ai/dsh-llm-pi-ai` / `@earendil-works/pi-ai`, which every DSH installation carries (`@deepseek-ai/dsh` → `dsh-base` → `dsh-llm-pi-ai`, with the `llm-pi-ai` row mounted unconditionally). If those packages cannot be resolved from the DSH installation, the plugin registers no route at all and **Active path** in Settings reads **unavailable**.
14
14
 
@@ -16,7 +16,7 @@ If the UI install fails (an unusual profile directory, say), the equivalent runs
16
16
 
17
17
  ```sh
18
18
  cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
19
- pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.7
19
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.8
20
20
  ```
21
21
 
22
22
  `dsh plugin --profile desktop …` is refused: the Electron application owns that profile exclusively.
@@ -69,6 +69,8 @@ For prefix caching, the plugin pins one stable `prompt_cache_key` per DSH sessio
69
69
 
70
70
  **No sign-in window.** Look for a clickable authorization link and code in the panel. A browser failing to open does not by itself prove `auth.x.ai` is unreachable; use “Open the sign-in page”. When a proxy is needed, set `https_proxy` in DSH's launch environment or `~/.dsh/.env`, then restart completely. `DSH_*` is a DSH bootstrap prefix, so declaring it in `~/.dsh/.env` makes that environment layer fail.
71
71
 
72
+ **Sign-in fails with `Cannot reach auth.x.ai` (2.0.4–2.0.7 showed `Grok subscription RPC "login/cli" timed out after 20000ms`).** The DSH process cannot connect to `auth.x.ai`. This is typical when the browser gets online through a proxy app's “system proxy” switch: DSH does not read the macOS or Windows system proxy setting, so a browser that opens x.ai does not mean DSH can. Add `https_proxy=http://127.0.0.1:<port>` to `~/.dsh/.env` (on Windows, `%USERPROFILE%\.dsh\.env`), using the proxy app's HTTP (or mixed) port; a `socks5://` URL is not supported and is skipped in favor of a direct connection. Save, then quit and restart DSH completely, or switch the proxy app to TUN mode. Conversations and usage (`cli-chat-proxy.grok.com`) depend on the same setting after sign-in.
73
+
72
74
  **`fetch failed` or `Billing request timed out`.** Check access to the account service and `cli-chat-proxy.grok.com` separately. When a proxy is needed, set standard `https_proxy` / `http_proxy` variables in DSH's launch environment or `~/.dsh/.env`, not the project directory's `.env`, then manually restart DSH. The plugin uses the host network setup rather than providing its own proxy service. Network conditions are one possibility; also check authentication and backend errors.
73
75
 
74
76
  **Empty model list.** Finish subscription sign-in, or select **Read saved session**. Check for an API-key-only entry and inspect catalog status without sharing raw responses.
@@ -95,7 +97,7 @@ In the desktop app the `desktop` profile is owned exclusively by the Electron ap
95
97
 
96
98
  ```sh
97
99
  cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
98
- pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.7
100
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.8
99
101
  ```
100
102
 
101
103
  Then quit and restart the desktop app completely. After a successful update the profile's `package.json` dependency should show the target version while its `dsh.profile.bundles` entry is unchanged. Refreshing the browser is not enough: without a Host restart the old version keeps loading.
@@ -8,7 +8,7 @@
8
8
 
9
9
  需要 DeepSeek Harness 桌面版,以及当前具有 Grok Build 使用资格的订阅账号。不需要安装 grok 命令。插件的 Node.js 要求为 `^22.19.0 || >=24.0.0`,DSH peer 声明以 [package.json](../package.json) 为准;其中 DSH 依赖使用 `>=0.1.5-rc.2 <0.3.0-0`,覆盖 0.1 与 0.2 全线。声明范围不等于所有版本都已经过实机验证。
10
10
 
11
- **安装:** 在桌面版打开 **设置 → 插件**,在安装输入框里填入包名 `dsh-grok-subscription` 并安装,然后**完全退出并重启桌面应用**。仅刷新页面不会重新加载 Host 中的适配器。需要可重复安装时,填入完整的 `包名@版本`,例如 `dsh-grok-subscription@2.0.7`。
11
+ **安装:** 在桌面版打开 **设置 → 插件**,在安装输入框里填入包名 `dsh-grok-subscription` 并安装,然后**完全退出并重启桌面应用**。仅刷新页面不会重新加载 Host 中的适配器。需要可重复安装时,填入完整的 `包名@版本`,例如 `dsh-grok-subscription@2.0.8`。
12
12
 
13
13
  **插件依赖宿主自带的 pi-ai 适配器。** 从 2.0.0 起,`grok-build` 路由完全由宿主的 `@deepseek-ai/dsh-llm-pi-ai` / `@earendil-works/pi-ai` 服务;DSH 的任何安装方式都带有它们(`@deepseek-ai/dsh` → `dsh-base` → `dsh-llm-pi-ai`,且 `llm-pi-ai` 行无条件挂载)。这两个包无法从 DSH 安装目录解析时,插件不会注册任何路由,设置页的「运行通路」显示**不可用**。
14
14
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  ```sh
18
18
  cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
19
- pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.7
19
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.8
20
20
  ```
21
21
 
22
22
  `dsh plugin --profile desktop …` 会被直接拒绝:桌面版 profile 由 Electron 应用独占。
@@ -69,6 +69,8 @@ grok-4.6
69
69
 
70
70
  **没有弹出登录窗口。** 看面板是否已经显示可点击的授权链接和验证码。浏览器没有弹出,不一定等于 `auth.x.ai` 不可达;点「打开登录页面」。需要代理时,在 DSH 启动环境或 `~/.dsh/.env` 设置 `https_proxy` 后完全重启。注意 `DSH_*` 是 DSH 的 bootstrap 前缀,写进 `~/.dsh/.env` 会让该环境层直接报错。
71
71
 
72
+ **点击登录后报 `Cannot reach auth.x.ai`(2.0.4–2.0.7 显示为 `Grok subscription RPC "login/cli" timed out after 20000ms`)。** DSH 进程连不上 `auth.x.ai`。常见于浏览器靠代理软件的“系统代理”开关上网的环境:DSH 不读取 macOS / Windows 的系统代理设置,所以浏览器能打开 x.ai 不代表 DSH 也能。在 `~/.dsh/.env`(Windows 为 `%USERPROFILE%\.dsh\.env`)写入 `https_proxy=http://127.0.0.1:<端口>`,端口填代理软件的 HTTP(或 mixed)端口;`socks5://` 不受支持,会被跳过并直连。保存后完全退出并重启 DSH,或改用代理软件的 TUN 模式。登录之后的对话与用量请求(`cli-chat-proxy.grok.com`)也依赖同一设置。
73
+
72
74
  **`fetch failed` / `Billing request timed out`。** 分别检查账号服务和 `cli-chat-proxy.grok.com` 的网络连接。需要代理时,在 DSH 启动环境或 `~/.dsh/.env` 中设置标准 `https_proxy` / `http_proxy`,而不是放进项目目录的 `.env`;再手动重启 DSH。插件沿用宿主网络配置,不提供单独代理服务。网络只是可能原因,还应检查认证和服务端错误。
73
75
 
74
76
  **模型列表为空。** 先完成订阅登录,或点击「读取已保存的会话」。检查是否只有 API-key 条目;查看目录状态,不要公开原始响应。
@@ -95,7 +97,7 @@ grok-4.6
95
97
 
96
98
  ```sh
97
99
  cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
98
- pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.7
100
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.8
99
101
  ```
100
102
 
101
103
  然后完全退出并重启桌面版。更新成功后该 profile 的 `package.json` 里依赖应变为目标版本,`dsh.profile.bundles` 条目保持不变。仅刷新网页不够:Host 未重启时仍会加载旧版本。
package/lib/client.js CHANGED
@@ -39,7 +39,7 @@ var import_react2 = require("react");
39
39
  var zh = {
40
40
  nav: "Grok \u8BA2\u9605",
41
41
  title: "Grok \u8BA2\u9605",
42
- subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 2.0.7",
42
+ subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 2.0.8",
43
43
  signedIn: "\u5DF2\u767B\u5F55",
44
44
  signedOut: "\u672A\u767B\u5F55",
45
45
  account: "\u8D26\u6237",
@@ -57,7 +57,7 @@ var zh = {
57
57
  deviceHint: "\u9762\u677F\u4F1A\u663E\u793A\u4E00\u6B21\u6027\u9A8C\u8BC1\u7801\u3002\u82E5\u6D4F\u89C8\u5668\u6CA1\u6709\u81EA\u52A8\u6253\u5F00\uFF0C\u70B9\u63D0\u793A\u91CC\u7684\u201C\u6253\u5F00\u767B\u5F55\u9875\u9762\u201D\uFF0C\u5E76\u5728\u9875\u9762\u4E0A\u786E\u8BA4\u540C\u4E00\u4E2A\u9A8C\u8BC1\u7801\u3002",
58
58
  loginWaiting: "\u7B49\u5F85\u6D4F\u89C8\u5668\u6388\u6743\u2026",
59
59
  loginOpenBrowser: "\u6253\u5F00\u767B\u5F55\u9875\u9762",
60
- loginNoUrl: "\u6CA1\u62FF\u5230\u767B\u5F55\u94FE\u63A5\uFF1A\u8BF7\u786E\u8BA4\u672C\u673A\u80FD\u8BBF\u95EE auth.x.ai\u3002\u684C\u9762\u7248\u9700\u8981\u4EE3\u7406\u65F6\uFF0C\u5728 ~/.dsh/.env \u8BBE\u7F6E https_proxy \u540E\u91CD\u542F\u3002",
60
+ loginUnreachable: "DSH \u8FDE\u4E0D\u4E0A auth.x.ai\u3002\u6D4F\u89C8\u5668\u80FD\u6253\u5F00 x.ai \u4E0D\u4EE3\u8868 DSH \u4E5F\u80FD\uFF1ADSH \u4E0D\u8BFB\u53D6\u7CFB\u7EDF\u4EE3\u7406\uFF08\u4F8B\u5982 Clash\u3001v2rayN \u7684\u201C\u7CFB\u7EDF\u4EE3\u7406\u201D\u5F00\u5173\uFF09\u3002\u8BF7\u5728 ~/.dsh/.env\uFF08Windows \u4E3A %USERPROFILE%\\.dsh\\.env\uFF09\u5199\u5165 https_proxy=http://127.0.0.1:\u7AEF\u53E3\uFF08\u586B\u4EE3\u7406\u8F6F\u4EF6\u7684 HTTP \u7AEF\u53E3\uFF0C\u4E0D\u652F\u6301 socks5://\uFF09\uFF0C\u7136\u540E\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F DSH\uFF1B\u4E5F\u53EF\u4EE5\u6539\u7528\u4EE3\u7406\u8F6F\u4EF6\u7684 TUN \u6A21\u5F0F\u3002",
61
61
  loginTimeout: "\u7B49\u5F85\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u70B9\u51FB\u767B\u5F55\u3002",
62
62
  pullHint: "\u5B89\u5168\u8BFB\u53D6 ~/.grok/auth.json\uFF08\u62D2\u7EDD\u7B26\u53F7\u94FE\u63A5\u4E0E\u7EC4/\u5176\u4ED6\u4EBA\u53EF\u8BFB\uFF09\u3002\u63D2\u4EF6\u767B\u5F55\u548C\u7EED\u671F\u4E5F\u4F1A\u5199\u56DE\u8FD9\u4E2A\u6587\u4EF6\uFF1B\u53EA\u628A\u77ED\u671F access token \u5199\u5165 DSH\u3002",
63
63
  loginHelp: "\u767B\u5F55\u65B9\u5F0F\u8BF4\u660E",
@@ -122,7 +122,7 @@ var zh = {
122
122
  var en = {
123
123
  nav: "Grok Subscription",
124
124
  title: "Grok Subscription",
125
- subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 2.0.7",
125
+ subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 2.0.8",
126
126
  signedIn: "Signed in",
127
127
  signedOut: "Signed out",
128
128
  account: "Account",
@@ -140,7 +140,7 @@ var en = {
140
140
  deviceHint: "The panel shows a one-time code. If no browser opened, use \u201COpen the sign-in page\u201D in the status line and confirm that same code.",
141
141
  loginWaiting: "Waiting for browser authorization\u2026",
142
142
  loginOpenBrowser: "Open the sign-in page",
143
- loginNoUrl: "No sign-in link was returned: make sure this machine can reach auth.x.ai. If the desktop app needs a proxy, set https_proxy in ~/.dsh/.env and restart.",
143
+ loginUnreachable: "DSH cannot reach auth.x.ai. A browser that opens x.ai does not mean DSH can: DSH ignores the system proxy setting (such as the \u201Csystem proxy\u201D switch in Clash or v2rayN). Add https_proxy=http://127.0.0.1:PORT to ~/.dsh/.env (on Windows, %USERPROFILE%\\.dsh\\.env), using your proxy app\u2019s HTTP port (socks5:// is not supported), then quit and restart DSH completely. A proxy app\u2019s TUN mode also works.",
144
144
  loginTimeout: "Timed out waiting for authorization; click sign-in again.",
145
145
  pullHint: "Reads ~/.grok/auth.json securely (refuses symlinks and group/other-readable files). Sign-in and renewal write this file too, and only the short-lived access token is stored in DSH.",
146
146
  loginHelp: "How signing in works",
@@ -905,6 +905,7 @@ function GrokSubscriptionSection({ rpc, t }) {
905
905
  const [busy, setBusy] = (0, import_react2.useState)("");
906
906
  const [usageBusy, setUsageBusy] = (0, import_react2.useState)(false);
907
907
  const [error, setError] = (0, import_react2.useState)("");
908
+ const [unreachable, setUnreachable] = (0, import_react2.useState)(false);
908
909
  const [notice, setNotice] = (0, import_react2.useState)("");
909
910
  const [status, setStatus] = (0, import_react2.useState)(void 0);
910
911
  const [loginPending, setLoginPending] = (0, import_react2.useState)(void 0);
@@ -975,6 +976,7 @@ function GrokSubscriptionSection({ rpc, t }) {
975
976
  setBusy(endpoint);
976
977
  }
977
978
  setError("");
979
+ setUnreachable(false);
978
980
  setNotice("");
979
981
  let value;
980
982
  try {
@@ -987,8 +989,9 @@ function GrokSubscriptionSection({ rpc, t }) {
987
989
  if (value?.ok === false && value.error) {
988
990
  setNotice("");
989
991
  setError(value.error);
992
+ if (isLogin && value.errorCode === "network") setUnreachable(true);
990
993
  } else if (value?.pending === true) {
991
- setLoginPending({ url: value.loginUrl, code: value.userCode, warning: value.warning });
994
+ setLoginPending({ url: value.loginUrl, code: value.userCode });
992
995
  setNotice("");
993
996
  } else if (value?.ok !== false) {
994
997
  const successKey = {
@@ -1005,8 +1008,10 @@ function GrokSubscriptionSection({ rpc, t }) {
1005
1008
  }
1006
1009
  }
1007
1010
  } catch (item) {
1011
+ const message = item instanceof Error ? item.message : String(item);
1008
1012
  setNotice("");
1009
- setError(item instanceof Error ? item.message : String(item));
1013
+ setError(message);
1014
+ if (isLogin && /timed out/iu.test(message)) setUnreachable(true);
1010
1015
  } finally {
1011
1016
  if (isUsageRefresh) setUsageBusy(false);
1012
1017
  else setBusy("");
@@ -1047,13 +1052,13 @@ function GrokSubscriptionSection({ rpc, t }) {
1047
1052
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { className: "gsLink", href: loginPending.url, target: "_blank", rel: "noreferrer", children: t("loginOpenBrowser") })
1048
1053
  ] }) : null
1049
1054
  ] }) : null,
1050
- loginPending?.warning ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--warn", children: t("loginNoUrl") }) : null,
1051
1055
  notice ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--ok", children: notice }) : null,
1052
1056
  error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsStatus gsStatus--error", children: [
1053
1057
  t("error"),
1054
1058
  ": ",
1055
1059
  error
1056
1060
  ] }) : null,
1061
+ error && unreachable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--warn", children: t("loginUnreachable") }) : null,
1057
1062
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DiagnosticsRows, { diagnostics: status?.diagnostics, t }),
1058
1063
  status?.diagnostics && !status.diagnostics.imageInput ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("adapterHint") }) : null,
1059
1064
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("details", { className: "gsDisclosure", children: [
package/lib/index.js CHANGED
@@ -39,6 +39,7 @@ var IMAGE_INPUT_MODEL_IDS = Object.freeze([
39
39
  var IMPORT_TIMEOUT_MS = 2500;
40
40
  var CREDENTIALS_IO_TIMEOUT_MS = 1500;
41
41
  var RPC_HANDLER_TIMEOUT_MS = 8e3;
42
+ var LOGIN_START_TIMEOUT_MS = 15e3;
42
43
  var LOGIN_HANDLER_TIMEOUT_MS = 2e4;
43
44
 
44
45
  // src/session.js
@@ -1302,15 +1303,67 @@ function timeoutSignal(ms) {
1302
1303
  }
1303
1304
  return void 0;
1304
1305
  }
1305
- async function postForm(url, params, fetchImpl) {
1306
- const response = await fetchImpl(url, {
1306
+ var OauthNetworkError = class extends Error {
1307
+ constructor(host, reason, options) {
1308
+ super(`Cannot reach ${host} (${reason})`, options);
1309
+ this.name = "OauthNetworkError";
1310
+ this.code = "network";
1311
+ this.host = host;
1312
+ }
1313
+ };
1314
+ var NETWORK_REASONS = Object.freeze({
1315
+ UND_ERR_CONNECT_TIMEOUT: "connection timed out",
1316
+ ETIMEDOUT: "connection timed out",
1317
+ UND_ERR_HEADERS_TIMEOUT: "no response from the server",
1318
+ UND_ERR_BODY_TIMEOUT: "the response stalled",
1319
+ ECONNREFUSED: "connection refused",
1320
+ ECONNRESET: "connection reset",
1321
+ UND_ERR_SOCKET: "connection closed",
1322
+ EPIPE: "connection closed",
1323
+ ENOTFOUND: "DNS lookup failed",
1324
+ EAI_AGAIN: "DNS lookup failed",
1325
+ ENETUNREACH: "network unreachable",
1326
+ EHOSTUNREACH: "host unreachable"
1327
+ });
1328
+ function networkCodeOf(error) {
1329
+ let current = error;
1330
+ for (let depth = 0; current && depth < 5; depth += 1) {
1331
+ if (typeof current.code === "string" && current.code) return current.code;
1332
+ current = current.cause ?? (Array.isArray(current.errors) ? current.errors[0] : void 0);
1333
+ }
1334
+ return void 0;
1335
+ }
1336
+ function networkReason(error) {
1337
+ const name2 = error?.name;
1338
+ if (name2 === "TimeoutError" || name2 === "AbortError") return "timed out";
1339
+ const code = networkCodeOf(error);
1340
+ if (code && NETWORK_REASONS[code]) return NETWORK_REASONS[code];
1341
+ if (code && /CERT|TLS|SSL/u.test(code)) return `TLS certificate rejected: ${code}`;
1342
+ return code ? `network error: ${code}` : "network error";
1343
+ }
1344
+ function hostOf(url) {
1345
+ try {
1346
+ return new URL(url).hostname;
1347
+ } catch {
1348
+ return "the sign-in server";
1349
+ }
1350
+ }
1351
+ async function fetchOrNetworkError(fetchImpl, url, init) {
1352
+ try {
1353
+ return await fetchImpl(url, init);
1354
+ } catch (error) {
1355
+ throw new OauthNetworkError(hostOf(url), networkReason(error), { cause: error });
1356
+ }
1357
+ }
1358
+ async function postForm(url, params, fetchImpl, signal) {
1359
+ const response = await fetchOrNetworkError(fetchImpl, url, {
1307
1360
  method: "POST",
1308
1361
  headers: {
1309
1362
  accept: "application/json",
1310
1363
  "content-type": "application/x-www-form-urlencoded"
1311
1364
  },
1312
1365
  body: new URLSearchParams(params).toString(),
1313
- signal: timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
1366
+ signal: signal ?? timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
1314
1367
  });
1315
1368
  const body = await readOauthBody(response);
1316
1369
  return { status: typeof response.status === "number" ? response.status : 0, body };
@@ -1350,11 +1403,12 @@ function fallbackOauthEndpoints(issuer = XAI_OAUTH_ISSUER) {
1350
1403
  async function discoverOauthEndpoints(issuer = XAI_OAUTH_ISSUER, options = {}) {
1351
1404
  const fetchImpl = options.fetch ?? fetch;
1352
1405
  const base = String(issuer).replace(/\/+$/u, "");
1406
+ const url = `${base}/.well-known/openid-configuration`;
1407
+ const response = await fetchOrNetworkError(fetchImpl, url, {
1408
+ headers: { accept: "application/json" },
1409
+ signal: options.signal ?? timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
1410
+ });
1353
1411
  try {
1354
- const response = await fetchImpl(`${base}/.well-known/openid-configuration`, {
1355
- headers: { accept: "application/json" },
1356
- signal: timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
1357
- });
1358
1412
  const body = await readOauthBody(response);
1359
1413
  const status = typeof response.status === "number" ? response.status : 0;
1360
1414
  if (status < 200 || status >= 300) throw new Error(`OIDC discovery failed (HTTP ${status})`);
@@ -1400,7 +1454,7 @@ async function requestDeviceAuthorization(options = {}) {
1400
1454
  const { status, body } = await postForm(endpoint, {
1401
1455
  client_id: clientId,
1402
1456
  scope: scopes
1403
- }, fetchImpl);
1457
+ }, fetchImpl, options.signal);
1404
1458
  if (body?.error || status < 200 || status >= 300) {
1405
1459
  throw new Error(`Could not start device sign-in: ${oauthError(body, status)}`);
1406
1460
  }
@@ -1582,6 +1636,7 @@ function createSessionService({
1582
1636
  authPath,
1583
1637
  env,
1584
1638
  oauthEndpoints,
1639
+ loginStartTimeoutMs = LOGIN_START_TIMEOUT_MS,
1585
1640
  now = () => Date.now()
1586
1641
  } = {}) {
1587
1642
  const renew = renewSession ?? (() => defaultRenewSession({
@@ -1834,17 +1889,28 @@ function createSessionService({
1834
1889
  const sleep = loginOptions?.sleep ?? defaultSleep;
1835
1890
  const openUrl = loginOptions?.openUrl ?? ((url) => openExternal(url, { spawn: loginOptions?.openSpawn }));
1836
1891
  const clock = () => typeof loginOptions?.now === "function" ? loginOptions.now() : now();
1892
+ const requestedStartMs = loginOptions?.startTimeoutMs ?? loginStartTimeoutMs;
1893
+ const startTimeoutMs = typeof requestedStartMs === "number" && requestedStartMs > 0 ? requestedStartMs : LOGIN_START_TIMEOUT_MS;
1894
+ const startSignal = loginOptions?.signal ?? timeoutSignal(startTimeoutMs);
1837
1895
  let endpoints;
1838
1896
  let device;
1839
1897
  try {
1840
- endpoints = loginOptions?.endpoints ?? oauthEndpoints ?? await discoverOauthEndpoints(XAI_OAUTH_ISSUER, { fetch: fetchLogin });
1898
+ endpoints = loginOptions?.endpoints ?? oauthEndpoints ?? await discoverOauthEndpoints(XAI_OAUTH_ISSUER, { fetch: fetchLogin, signal: startSignal });
1841
1899
  device = await requestDeviceAuthorization({
1842
1900
  endpoint: endpoints.deviceAuthorizationEndpoint,
1843
- fetch: fetchLogin
1901
+ fetch: fetchLogin,
1902
+ signal: startSignal
1844
1903
  });
1845
1904
  } catch (error) {
1846
1905
  const message = error instanceof Error ? error.message : "Could not start sign-in";
1847
- return { ok: false, error: message, ...await status() };
1906
+ const network = error?.code === "network";
1907
+ if (network) logger?.warn?.("Grok subscription sign-in could not start: %s", message);
1908
+ return {
1909
+ ok: false,
1910
+ error: message,
1911
+ ...network ? { errorCode: "network" } : {},
1912
+ ...await status()
1913
+ };
1848
1914
  }
1849
1915
  const loginUrl = device.verificationUriComplete || device.verificationUri;
1850
1916
  if (!isXaiHttpsUrl(loginUrl) || !device.userCode || !device.deviceCode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-grok-subscription",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "Use a SuperGrok or X Premium Grok Build subscription in DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",