dsh-qoder-connect 0.2.2 → 0.2.3-alpha.2
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/LICENSE +21 -21
- package/README.en.md +177 -177
- package/README.md +178 -178
- package/cordis.patch.yml +6 -6
- package/lib/bin.js +1 -1
- package/lib/client.js +13 -6
- package/lib/index.d.ts +23 -9
- package/lib/index.js +13 -17
- package/lib/{variants-CFI6-cGn.js → variants-UX2MxZaz.js} +269 -52
- package/package.json +120 -120
package/README.md
CHANGED
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
# DSH Qoder Connect
|
|
2
|
-
|
|
3
|
-
[English](./README.en.md) | **简体中文**
|
|
4
|
-
|
|
5
|
-
把 **Qoder 订阅**的模型以个人访问令牌(PAT)接入 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)的社区插件。一个插件同时服务两条 Qoder 产品线,各自一张设置卡片、一个 provider、一套凭证与模型目录,互不混用:
|
|
6
|
-
|
|
7
|
-
| 变体 | provider id | 卡片标题(中文界面) | PAT 生成站点 |
|
|
8
|
-
|---|---|---|---|
|
|
9
|
-
| 国内版 | `qoder` | Qoder(国内版) | qoder.com.cn |
|
|
10
|
-
| 国际版 | `qoder-global` | Qoder Global(国际版) | qoder.com |
|
|
11
|
-
|
|
12
|
-
只需要哪一版就只配哪一版:未保存 PAT 的变体不显示模型分组,另一版完全不受影响。流式输出、推理内容、工具调用走插件内置的 Qoder 传输层;对话循环、压缩与权限始终由 DSH 本体掌控。
|
|
13
|
-
|
|
14
|
-
## 功能
|
|
15
|
-
|
|
16
|
-
- **PAT 即存即用**:在卡片上粘贴 PAT 点「保存」,插件先对该变体的区域实时校验,通过才落盘,模型分组立即出现,无需重启 DSH。已登录后可「更换 PAT」(新令牌校验通过才覆盖,粘贴打错不会把可用凭据弄丢)与「清除 PAT」。
|
|
17
|
-
- **双变体独立配置**:`qoder`(china)与 `qoder-global`(global)各有自己的凭证文件、路由与已保存目录——qoder.com 生成的令牌对中国区无效,反之亦然,两套永不互串。
|
|
18
|
-
- **模型目录三级降级(live → saved → fallback)**:卡片明示当前列表来自哪里——「模型列表更新于 …」(实时拉取)、「当前显示已保存的模型列表,更新于 …」(本账号上次成功目录,重启或断网后顶上来)、「当前显示内置模型列表(尚未从 Qoder 更新)」(编译进插件的兜底名单);最近一次拉取失败时附带原因,并可在卡片上手动「刷新模型列表」。
|
|
19
|
-
- **上下文窗口一键切换**:两张变体卡片的「上下文窗口」页列出每个模型的容量(默认值与上游声明的最大值),并各自提供「使用上游声明的最大上下文窗口」开关(默认勾选):勾选按最大窗口(如 Qwen3.8-Max 的 1M)发请求,不勾选按默认窗口(200K),两版开关状态独立保存、互不影响。
|
|
20
|
-
- **模型自由开关与批量管理**:两张变体卡片均提供「模型开关」页(位于上下文窗口与额度明细之间),可自由开启或关闭指定模型;关闭的模型将自动从 DeepSeek Harness 的模型选择器中隐藏,避免误选与列表冗长;顶部提供按名称/ID的即时搜索框,并支持全选与一键「批量开启」/「批量关闭」。
|
|
21
|
-
- **每日自动签到与日志明细**:支持每天自动签到领取 100 Credits 算力额度,**签到时刻可在卡片上自定义**(默认 10:00,即上游刷新时间,按 UTC+8),配备双端开关与开机防漏补签机制;卡片常驻「签到日志」面板,提供执行流水记录与「立即签到」、「刷新」、「清空日志」快捷操作。
|
|
22
|
-
- **侧栏额度展示 + 额度明细**:「Qoder 侧栏展示」卡片可分别为两版开启侧栏底部的额度小卡(默认关,开启前需为该变体保存 PAT),共享一个刷新间隔(默认 5 分钟,最小 1 分钟)。点侧栏小卡在中间面板打开「Qoder 额度」明细:按资源包逐行给出「剩余 / 总量 + 进度条|到期时间」,合计占比与本轮重置时间一并展示;再点同一张卡关闭,点另一张切换到对应版本。
|
|
23
|
-
- **倍率显示 `x<priceFactor>`**:模型名后直接拼上游报价的价格倍率(如 `某模型 · x0.79`,免费为 `x0`),来自目录接口的 `price_factor` 字段,仅作展示、不影响请求;上游未报倍率的模型不显示后缀,不会凭空补一个数。
|
|
24
|
-
|
|
25
|
-
## 界面一览
|
|
26
|
-
|
|
27
|
-
### 侧栏额度小卡与额度明细
|
|
28
|
-
|
|
29
|
-
在「Qoder 侧栏展示」卡片开启后,侧栏底部出现该变体的额度小卡;点击小卡在中间面板打开「Qoder 额度」明细,按套餐逐行展示,可随时在两版之间切换。
|
|
30
|
-
|
|
31
|
-
**国内版(侧栏点击后):**
|
|
32
|
-
|
|
33
|
-

|
|
34
|
-
|
|
35
|
-
**国际版(侧栏点击后):**
|
|
36
|
-
|
|
37
|
-

|
|
38
|
-
|
|
39
|
-
### 侧栏开关设置
|
|
40
|
-
|
|
41
|
-
「Qoder 侧栏展示」卡片控制两张侧栏小卡的显隐与刷新间隔(间隔对两卡同时生效):
|
|
42
|
-
|
|
43
|
-

|
|
44
|
-
|
|
45
|
-
### 插件面板内的设置入口
|
|
46
|
-
|
|
47
|
-
同一张 Qoder 设置卡片还有第二个入口:左侧边栏「插件」面板 → 已安装 → 「Qoder 连接器」卡片,点开卡片详情页,完整设置就挂在插件的描述与组件列表之间(PAT、双区域开关、上下文窗口、模型开关、签到与侧栏额度设置,与「设置 → 插件设置」里完全一致,两处共用同一份配置)。宿主不提供该槽位时(旧版 DSH)只用「设置 → 插件设置」一个入口,功能不受影响。
|
|
48
|
-
|
|
49
|
-
### 会话内选择模型
|
|
50
|
-
|
|
51
|
-
保存 PAT 后,模型选择器出现 Qoder 分组,模型名后跟随价格倍率(`x0.5`、`x0` 等):
|
|
52
|
-
|
|
53
|
-

|
|
54
|
-
|
|
55
|
-
### 上下文窗口设置
|
|
56
|
-
|
|
57
|
-
「上下文窗口」页按模型列出容量与最大声明值,顶部是「使用上游声明的最大上下文窗口」开关:
|
|
58
|
-
|
|
59
|
-
**国内版:**
|
|
60
|
-
|
|
61
|
-

|
|
62
|
-
|
|
63
|
-
**国际版:**
|
|
64
|
-
|
|
65
|
-

|
|
66
|
-
|
|
67
|
-
### 模型开关与批量控制
|
|
68
|
-
|
|
69
|
-
两张变体卡片均提供「模型开关」页(位于「上下文窗口」与「额度明细」之间):支持即时搜索模型、单独开启/关闭指定模型,以及全选后一键「批量开启」或「批量关闭」;关闭的模型不会出现在模型选择器中。
|
|
70
|
-
|
|
71
|
-

|
|
72
|
-
|
|
73
|
-
### 每日自动签到与签到日志
|
|
74
|
-
|
|
75
|
-
支持在设置卡片中开启每日自动签到,并自定义各版本的签到时刻(默认 10:00,按 UTC+8);在卡片底部的「签到日志」面板中查看签到流水、立即签到或清空日志:
|
|
76
|
-
|
|
77
|
-

|
|
78
|
-
|
|
79
|
-
## 安装
|
|
80
|
-
|
|
81
|
-
前置条件:
|
|
82
|
-
|
|
83
|
-
- DSH 核心 `0.1.7-rc.1` 及以上(本插件 peer 依赖 `@deepseek-ai/*` `>=0.1.7-rc.1` 线);
|
|
84
|
-
- Node.js `^22.19.0 || >=24.0.0`(`package.json` engines);
|
|
85
|
-
- 你自己的 Qoder 账号,以及至少一枚个人访问令牌。
|
|
86
|
-
|
|
87
|
-
```sh
|
|
88
|
-
# 从 GitHub 安装(推荐)
|
|
89
|
-
dsh plugin --profile web add github:masknull/dsh-qoder-connect
|
|
90
|
-
# 或从 npm 安装
|
|
91
|
-
dsh plugin --profile web add dsh-qoder-connect
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
按你使用的 profile 换 `--profile` 的值(`web` / `desktop` / `tui`,如 `dsh plugin --profile desktop add dsh-qoder-connect`)。装到哪个 profile,数据就落在哪个 profile 目录里,web / desktop / tui 各自独立。没有浏览器卡片的终端环境(TUI)下依旧可用 CLI 保存 PAT 并驱动模型;设置卡片与侧栏额度卡需要 Web/Desktop 界面。
|
|
95
|
-
|
|
96
|
-
> **DSH 低于 `0.1.7-rc.1`?** 请改用最后一个支持 0.1.5 线的版本 **[v0.2.0](https://github.com/masknull/dsh-qoder-connect/releases/tag/v0.2.0)**(要求 DSH ≥ `0.1.5-rc.2`):
|
|
97
|
-
>
|
|
98
|
-
> ```sh
|
|
99
|
-
> # GitHub(推荐,指定 tag)
|
|
100
|
-
> dsh plugin --profile web add github:masknull/dsh-qoder-connect#v0.2.0
|
|
101
|
-
> # 或从 npm 安装
|
|
102
|
-
> dsh plugin --profile web add dsh-qoder-connect@0.2.0
|
|
103
|
-
> ```
|
|
104
|
-
>
|
|
105
|
-
> 从 0.1.5 升级上来的老用户:本版本已移除旧宿主 `settings.yaml` 的自动迁移;如需保留旧配置,请先经 **[v0.1.9](https://github.com/masknull/dsh-qoder-connect/releases/tag/v0.1.9)**(首个带数据迁移的版本)完成迁移后,再升级本版本。
|
|
106
|
-
|
|
107
|
-
## 配置与使用
|
|
108
|
-
|
|
109
|
-
### 生成 PAT
|
|
110
|
-
|
|
111
|
-
- **Qoder(国内版)**:登录 qoder.com.cn → 账号设置 → 个人访问令牌,生成后复制。[直达获取](https://qoder.cn/account/integrations)
|
|
112
|
-
- **Qoder Global(国际版)**:登录 qoder.com → 账号设置 → 个人访问令牌,生成后复制。[直达获取](https://qoder.com/account/integrations)
|
|
113
|
-
|
|
114
|
-
(卡片内也有同样指引:「请先在 qoder.com.cn 的账号设置(账号 → 个人访问令牌)中生成 PAT,再粘贴到下方输入框。」)
|
|
115
|
-
|
|
116
|
-
### 在卡片上保存
|
|
117
|
-
|
|
118
|
-
DSH → 设置 → 插件设置 → Qoder 卡片,展开后把 PAT 粘贴进密码输入框,点「保存」(期间显示「校验并保存中…」)。保存动作会先经 Qoder 校验令牌对该变体的区域有效,失败则不落盘并提示「PAT 无效或已过期 — 请到账号设置重新生成后再试。」两版各填各的卡片即可两组并存。
|
|
119
|
-
|
|
120
|
-
### 环境变量兜底
|
|
121
|
-
|
|
122
|
-
不方便走卡片的场景可用环境变量:**未保存过凭证文件**时插件读取 env 兜底——
|
|
123
|
-
|
|
124
|
-
| 变体 | 环境变量 |
|
|
125
|
-
|---|---|
|
|
126
|
-
| `qoder`(china) | `QODER_CN_PERSONAL_ACCESS_TOKEN` |
|
|
127
|
-
| `qoder-global` | `QODER_PERSONAL_ACCESS_TOKEN` |
|
|
128
|
-
|
|
129
|
-
优先级为 **文件 > env**:只要该变体保存过有效的凭证文件,环境变量就不再生效(保存的令牌永远压过游离的 env)。env 来源的令牌在卡片上标为「来源:环境变量」,且没有保存时间。
|
|
130
|
-
|
|
131
|
-
## 数据与隐私
|
|
132
|
-
|
|
133
|
-
### 文件布局
|
|
134
|
-
|
|
135
|
-
插件的全部自有文件收在数据目录一处:`<profile>/.dsh-qoder-connect/`(凭据在根,可重建的缓存在 `state/` 子目录,「清缓存」永远碰不到凭据):
|
|
136
|
-
|
|
137
|
-
```text
|
|
138
|
-
<profile>/.dsh-qoder-connect/
|
|
139
|
-
├── .qoder-auth.json # 国内版 PAT({version:2, pat, region, savedAt})
|
|
140
|
-
├── .qoder-global-auth.json # 国际版 PAT
|
|
141
|
-
├── checkin-status.json # 签到状态与历史日志记录
|
|
142
|
-
└── state/
|
|
143
|
-
├── .qoder-catalog.json # 国内版按账号保存的模型目录
|
|
144
|
-
├── .qoder-global-catalog.json
|
|
145
|
-
├── .qoder-probe.json # 国内版推理档位探测记录
|
|
146
|
-
├── .qoder-global-probe.json
|
|
147
|
-
├── .qoder-host-heartbeat.json # host 心跳(doctor 判断宿主是否在跑)
|
|
148
|
-
└── .qoder-machine-id # 传输层机器标识种子(不落 ~/.qoder)
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
数据目录按 profile 归属(在 `$DSH_HOME/profiles/` 下找声明本插件的 profile),无法确定时回落到 Harness home;环境变量 `DSH_QODER_DATA_DIR` 可显式覆盖。
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
## 开发
|
|
155
|
-
|
|
156
|
-
```sh
|
|
157
|
-
pnpm install
|
|
158
|
-
pnpm run build # tsdown 产出 lib/(宿主 bundle + client bundle)
|
|
159
|
-
pnpm test # vitest run(tests/**/*.spec.ts)
|
|
160
|
-
pnpm run test:qoder # node --test(tests/qoder/*.test.ts,Qoder 传输层用例)
|
|
161
|
-
pnpm run typecheck # tsc:宿主 + client 两份 tsconfig
|
|
162
|
-
pnpm run check # typecheck + test + test:qoder + build 一条命令全过
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
Qoder 传输层的上游 `node:test` 用例已移植进 `tests/qoder/`(用 `pnpm run test:qoder` 跑,原件 `reference-qoder-tests/` 已移除);命令以 `package.json` scripts 为准。
|
|
166
|
-
|
|
167
|
-
## 出处与许可
|
|
168
|
-
|
|
169
|
-
- 骨架与 connect 机制改造自 [masknull/dsh-workbuddy-connect](https://github.com/masknull/dsh-workbuddy-connect)(MIT);
|
|
170
|
-
- Qoder 传输层移植自 [mo-n/dsh-provider-qoder](https://github.com/mo-n/dsh-provider-qoder)(MIT)。
|
|
171
|
-
|
|
172
|
-
本项目按 [MIT](./LICENSE) 许可发布,双上游出处详见 [NOTICE](./NOTICE)。本仓库与 Qoder、DeepSeek 官方均无关联,是社区适配器——非官方实现。
|
|
173
|
-
|
|
174
|
-
## 免责声明
|
|
175
|
-
|
|
176
|
-
- Qoder 的接口来自上游仓库:上游随时可能改动、限流或封禁调用方,导致本插件部分或全部功能失效;届时可能只有等社区跟进,不保证兼容窗口。
|
|
177
|
-
- 本工具仅供个人学习研究,用于驱动**使用者自己的** Qoder 账号;请勿用于商业转售或任何超出个人合理使用的场景。使用即表示你自行承担账号被限制、额度损失、服务中断等一切后果,并遵守 Qoder 的服务条款。
|
|
178
|
-
- 作者不对因使用或滥用本插件产生的任何直接或间接损失负责。文中出现的 Qoder、DeepSeek 等名称与商标归各自权利人所有,仅作兼容性描述之用。
|
|
1
|
+
# DSH Qoder Connect
|
|
2
|
+
|
|
3
|
+
[English](./README.en.md) | **简体中文**
|
|
4
|
+
|
|
5
|
+
把 **Qoder 订阅**的模型以个人访问令牌(PAT)接入 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)的社区插件。一个插件同时服务两条 Qoder 产品线,各自一张设置卡片、一个 provider、一套凭证与模型目录,互不混用:
|
|
6
|
+
|
|
7
|
+
| 变体 | provider id | 卡片标题(中文界面) | PAT 生成站点 |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| 国内版 | `qoder` | Qoder(国内版) | qoder.com.cn |
|
|
10
|
+
| 国际版 | `qoder-global` | Qoder Global(国际版) | qoder.com |
|
|
11
|
+
|
|
12
|
+
只需要哪一版就只配哪一版:未保存 PAT 的变体不显示模型分组,另一版完全不受影响。流式输出、推理内容、工具调用走插件内置的 Qoder 传输层;对话循环、压缩与权限始终由 DSH 本体掌控。
|
|
13
|
+
|
|
14
|
+
## 功能
|
|
15
|
+
|
|
16
|
+
- **PAT 即存即用**:在卡片上粘贴 PAT 点「保存」,插件先对该变体的区域实时校验,通过才落盘,模型分组立即出现,无需重启 DSH。已登录后可「更换 PAT」(新令牌校验通过才覆盖,粘贴打错不会把可用凭据弄丢)与「清除 PAT」。
|
|
17
|
+
- **双变体独立配置**:`qoder`(china)与 `qoder-global`(global)各有自己的凭证文件、路由与已保存目录——qoder.com 生成的令牌对中国区无效,反之亦然,两套永不互串。
|
|
18
|
+
- **模型目录三级降级(live → saved → fallback)**:卡片明示当前列表来自哪里——「模型列表更新于 …」(实时拉取)、「当前显示已保存的模型列表,更新于 …」(本账号上次成功目录,重启或断网后顶上来)、「当前显示内置模型列表(尚未从 Qoder 更新)」(编译进插件的兜底名单);最近一次拉取失败时附带原因,并可在卡片上手动「刷新模型列表」。
|
|
19
|
+
- **上下文窗口一键切换**:两张变体卡片的「上下文窗口」页列出每个模型的容量(默认值与上游声明的最大值),并各自提供「使用上游声明的最大上下文窗口」开关(默认勾选):勾选按最大窗口(如 Qwen3.8-Max 的 1M)发请求,不勾选按默认窗口(200K),两版开关状态独立保存、互不影响。
|
|
20
|
+
- **模型自由开关与批量管理**:两张变体卡片均提供「模型开关」页(位于上下文窗口与额度明细之间),可自由开启或关闭指定模型;关闭的模型将自动从 DeepSeek Harness 的模型选择器中隐藏,避免误选与列表冗长;顶部提供按名称/ID的即时搜索框,并支持全选与一键「批量开启」/「批量关闭」。
|
|
21
|
+
- **每日自动签到与日志明细**:支持每天自动签到领取 100 Credits 算力额度,**签到时刻可在卡片上自定义**(默认 10:00,即上游刷新时间,按 UTC+8),配备双端开关与开机防漏补签机制;卡片常驻「签到日志」面板,提供执行流水记录与「立即签到」、「刷新」、「清空日志」快捷操作。
|
|
22
|
+
- **侧栏额度展示 + 额度明细**:「Qoder 侧栏展示」卡片可分别为两版开启侧栏底部的额度小卡(默认关,开启前需为该变体保存 PAT),共享一个刷新间隔(默认 5 分钟,最小 1 分钟)。点侧栏小卡在中间面板打开「Qoder 额度」明细:按资源包逐行给出「剩余 / 总量 + 进度条|到期时间」,合计占比与本轮重置时间一并展示;再点同一张卡关闭,点另一张切换到对应版本。
|
|
23
|
+
- **倍率显示 `x<priceFactor>`**:模型名后直接拼上游报价的价格倍率(如 `某模型 · x0.79`,免费为 `x0`),来自目录接口的 `price_factor` 字段,仅作展示、不影响请求;上游未报倍率的模型不显示后缀,不会凭空补一个数。
|
|
24
|
+
|
|
25
|
+
## 界面一览
|
|
26
|
+
|
|
27
|
+
### 侧栏额度小卡与额度明细
|
|
28
|
+
|
|
29
|
+
在「Qoder 侧栏展示」卡片开启后,侧栏底部出现该变体的额度小卡;点击小卡在中间面板打开「Qoder 额度」明细,按套餐逐行展示,可随时在两版之间切换。
|
|
30
|
+
|
|
31
|
+
**国内版(侧栏点击后):**
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
**国际版(侧栏点击后):**
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
### 侧栏开关设置
|
|
40
|
+
|
|
41
|
+
「Qoder 侧栏展示」卡片控制两张侧栏小卡的显隐与刷新间隔(间隔对两卡同时生效):
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
### 插件面板内的设置入口
|
|
46
|
+
|
|
47
|
+
同一张 Qoder 设置卡片还有第二个入口:左侧边栏「插件」面板 → 已安装 → 「Qoder 连接器」卡片,点开卡片详情页,完整设置就挂在插件的描述与组件列表之间(PAT、双区域开关、上下文窗口、模型开关、签到与侧栏额度设置,与「设置 → 插件设置」里完全一致,两处共用同一份配置)。宿主不提供该槽位时(旧版 DSH)只用「设置 → 插件设置」一个入口,功能不受影响。
|
|
48
|
+
|
|
49
|
+
### 会话内选择模型
|
|
50
|
+
|
|
51
|
+
保存 PAT 后,模型选择器出现 Qoder 分组,模型名后跟随价格倍率(`x0.5`、`x0` 等):
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
### 上下文窗口设置
|
|
56
|
+
|
|
57
|
+
「上下文窗口」页按模型列出容量与最大声明值,顶部是「使用上游声明的最大上下文窗口」开关:
|
|
58
|
+
|
|
59
|
+
**国内版:**
|
|
60
|
+
|
|
61
|
+

|
|
62
|
+
|
|
63
|
+
**国际版:**
|
|
64
|
+
|
|
65
|
+

|
|
66
|
+
|
|
67
|
+
### 模型开关与批量控制
|
|
68
|
+
|
|
69
|
+
两张变体卡片均提供「模型开关」页(位于「上下文窗口」与「额度明细」之间):支持即时搜索模型、单独开启/关闭指定模型,以及全选后一键「批量开启」或「批量关闭」;关闭的模型不会出现在模型选择器中。
|
|
70
|
+
|
|
71
|
+

|
|
72
|
+
|
|
73
|
+
### 每日自动签到与签到日志
|
|
74
|
+
|
|
75
|
+
支持在设置卡片中开启每日自动签到,并自定义各版本的签到时刻(默认 10:00,按 UTC+8);在卡片底部的「签到日志」面板中查看签到流水、立即签到或清空日志:
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
|
|
79
|
+
## 安装
|
|
80
|
+
|
|
81
|
+
前置条件:
|
|
82
|
+
|
|
83
|
+
- DSH 核心 `0.1.7-rc.1` 及以上(本插件 peer 依赖 `@deepseek-ai/*` `>=0.1.7-rc.1` 线);
|
|
84
|
+
- Node.js `^22.19.0 || >=24.0.0`(`package.json` engines);
|
|
85
|
+
- 你自己的 Qoder 账号,以及至少一枚个人访问令牌。
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
# 从 GitHub 安装(推荐)
|
|
89
|
+
dsh plugin --profile web add github:masknull/dsh-qoder-connect
|
|
90
|
+
# 或从 npm 安装
|
|
91
|
+
dsh plugin --profile web add dsh-qoder-connect
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
按你使用的 profile 换 `--profile` 的值(`web` / `desktop` / `tui`,如 `dsh plugin --profile desktop add dsh-qoder-connect`)。装到哪个 profile,数据就落在哪个 profile 目录里,web / desktop / tui 各自独立。没有浏览器卡片的终端环境(TUI)下依旧可用 CLI 保存 PAT 并驱动模型;设置卡片与侧栏额度卡需要 Web/Desktop 界面。
|
|
95
|
+
|
|
96
|
+
> **DSH 低于 `0.1.7-rc.1`?** 请改用最后一个支持 0.1.5 线的版本 **[v0.2.0](https://github.com/masknull/dsh-qoder-connect/releases/tag/v0.2.0)**(要求 DSH ≥ `0.1.5-rc.2`):
|
|
97
|
+
>
|
|
98
|
+
> ```sh
|
|
99
|
+
> # GitHub(推荐,指定 tag)
|
|
100
|
+
> dsh plugin --profile web add github:masknull/dsh-qoder-connect#v0.2.0
|
|
101
|
+
> # 或从 npm 安装
|
|
102
|
+
> dsh plugin --profile web add dsh-qoder-connect@0.2.0
|
|
103
|
+
> ```
|
|
104
|
+
>
|
|
105
|
+
> 从 0.1.5 升级上来的老用户:本版本已移除旧宿主 `settings.yaml` 的自动迁移;如需保留旧配置,请先经 **[v0.1.9](https://github.com/masknull/dsh-qoder-connect/releases/tag/v0.1.9)**(首个带数据迁移的版本)完成迁移后,再升级本版本。
|
|
106
|
+
|
|
107
|
+
## 配置与使用
|
|
108
|
+
|
|
109
|
+
### 生成 PAT
|
|
110
|
+
|
|
111
|
+
- **Qoder(国内版)**:登录 qoder.com.cn → 账号设置 → 个人访问令牌,生成后复制。[直达获取](https://qoder.cn/account/integrations)
|
|
112
|
+
- **Qoder Global(国际版)**:登录 qoder.com → 账号设置 → 个人访问令牌,生成后复制。[直达获取](https://qoder.com/account/integrations)
|
|
113
|
+
|
|
114
|
+
(卡片内也有同样指引:「请先在 qoder.com.cn 的账号设置(账号 → 个人访问令牌)中生成 PAT,再粘贴到下方输入框。」)
|
|
115
|
+
|
|
116
|
+
### 在卡片上保存
|
|
117
|
+
|
|
118
|
+
DSH → 设置 → 插件设置 → Qoder 卡片,展开后把 PAT 粘贴进密码输入框,点「保存」(期间显示「校验并保存中…」)。保存动作会先经 Qoder 校验令牌对该变体的区域有效,失败则不落盘并提示「PAT 无效或已过期 — 请到账号设置重新生成后再试。」两版各填各的卡片即可两组并存。
|
|
119
|
+
|
|
120
|
+
### 环境变量兜底
|
|
121
|
+
|
|
122
|
+
不方便走卡片的场景可用环境变量:**未保存过凭证文件**时插件读取 env 兜底——
|
|
123
|
+
|
|
124
|
+
| 变体 | 环境变量 |
|
|
125
|
+
|---|---|
|
|
126
|
+
| `qoder`(china) | `QODER_CN_PERSONAL_ACCESS_TOKEN` |
|
|
127
|
+
| `qoder-global` | `QODER_PERSONAL_ACCESS_TOKEN` |
|
|
128
|
+
|
|
129
|
+
优先级为 **文件 > env**:只要该变体保存过有效的凭证文件,环境变量就不再生效(保存的令牌永远压过游离的 env)。env 来源的令牌在卡片上标为「来源:环境变量」,且没有保存时间。
|
|
130
|
+
|
|
131
|
+
## 数据与隐私
|
|
132
|
+
|
|
133
|
+
### 文件布局
|
|
134
|
+
|
|
135
|
+
插件的全部自有文件收在数据目录一处:`<profile>/.dsh-qoder-connect/`(凭据在根,可重建的缓存在 `state/` 子目录,「清缓存」永远碰不到凭据):
|
|
136
|
+
|
|
137
|
+
```text
|
|
138
|
+
<profile>/.dsh-qoder-connect/
|
|
139
|
+
├── .qoder-auth.json # 国内版 PAT({version:2, pat, region, savedAt})
|
|
140
|
+
├── .qoder-global-auth.json # 国际版 PAT
|
|
141
|
+
├── checkin-status.json # 签到状态与历史日志记录
|
|
142
|
+
└── state/
|
|
143
|
+
├── .qoder-catalog.json # 国内版按账号保存的模型目录
|
|
144
|
+
├── .qoder-global-catalog.json
|
|
145
|
+
├── .qoder-probe.json # 国内版推理档位探测记录
|
|
146
|
+
├── .qoder-global-probe.json
|
|
147
|
+
├── .qoder-host-heartbeat.json # host 心跳(doctor 判断宿主是否在跑)
|
|
148
|
+
└── .qoder-machine-id # 传输层机器标识种子(不落 ~/.qoder)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
数据目录按 profile 归属(在 `$DSH_HOME/profiles/` 下找声明本插件的 profile),无法确定时回落到 Harness home;环境变量 `DSH_QODER_DATA_DIR` 可显式覆盖。
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
## 开发
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
pnpm install
|
|
158
|
+
pnpm run build # tsdown 产出 lib/(宿主 bundle + client bundle)
|
|
159
|
+
pnpm test # vitest run(tests/**/*.spec.ts)
|
|
160
|
+
pnpm run test:qoder # node --test(tests/qoder/*.test.ts,Qoder 传输层用例)
|
|
161
|
+
pnpm run typecheck # tsc:宿主 + client 两份 tsconfig
|
|
162
|
+
pnpm run check # typecheck + test + test:qoder + build 一条命令全过
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Qoder 传输层的上游 `node:test` 用例已移植进 `tests/qoder/`(用 `pnpm run test:qoder` 跑,原件 `reference-qoder-tests/` 已移除);命令以 `package.json` scripts 为准。
|
|
166
|
+
|
|
167
|
+
## 出处与许可
|
|
168
|
+
|
|
169
|
+
- 骨架与 connect 机制改造自 [masknull/dsh-workbuddy-connect](https://github.com/masknull/dsh-workbuddy-connect)(MIT);
|
|
170
|
+
- Qoder 传输层移植自 [mo-n/dsh-provider-qoder](https://github.com/mo-n/dsh-provider-qoder)(MIT)。
|
|
171
|
+
|
|
172
|
+
本项目按 [MIT](./LICENSE) 许可发布,双上游出处详见 [NOTICE](./NOTICE)。本仓库与 Qoder、DeepSeek 官方均无关联,是社区适配器——非官方实现。
|
|
173
|
+
|
|
174
|
+
## 免责声明
|
|
175
|
+
|
|
176
|
+
- Qoder 的接口来自上游仓库:上游随时可能改动、限流或封禁调用方,导致本插件部分或全部功能失效;届时可能只有等社区跟进,不保证兼容窗口。
|
|
177
|
+
- 本工具仅供个人学习研究,用于驱动**使用者自己的** Qoder 账号;请勿用于商业转售或任何超出个人合理使用的场景。使用即表示你自行承担账号被限制、额度损失、服务中断等一切后果,并遵守 Qoder 的服务条款。
|
|
178
|
+
- 作者不对因使用或滥用本插件产生的任何直接或间接损失负责。文中出现的 Qoder、DeepSeek 等名称与商标归各自权利人所有,仅作兼容性描述之用。
|
package/cordis.patch.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Register the Qoder provider without changing the profile's current
|
|
2
|
-
# default model. Credentials are Personal Access Tokens (PATs) the user
|
|
3
|
-
# pastes into the plugin's card; no OAuth or device flow is started.
|
|
4
|
-
- insert:
|
|
5
|
-
- id: llm-qoder
|
|
6
|
-
name: dsh-qoder-connect
|
|
1
|
+
# Register the Qoder provider without changing the profile's current
|
|
2
|
+
# default model. Credentials are Personal Access Tokens (PATs) the user
|
|
3
|
+
# pastes into the plugin's card; no OAuth or device flow is started.
|
|
4
|
+
- insert:
|
|
5
|
+
- id: llm-qoder
|
|
6
|
+
name: dsh-qoder-connect
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { D as
|
|
2
|
+
import { D as QoderCatalogStore, O as qoderCatalogPath, U as qoderCredentialIdentity, V as QoderCredentialStore, b as createQoderTransport, f as QODER_CONNECT_VERSION, i as variantFor, k as FALLBACK_QODER_MODELS, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, y as validateApiKey } from "./variants-UX2MxZaz.js";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { realpathSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
package/lib/client.js
CHANGED
|
@@ -422,10 +422,16 @@ window.__ModuleLoader__.load({
|
|
|
422
422
|
const POLL_MIN_MS = 6e4;
|
|
423
423
|
/** 10:00 UTC+8, the moment the upstream resets the daily campaign. */
|
|
424
424
|
const CHECK_IN_MINUTE_DEFAULT = 600;
|
|
425
|
+
/**
|
|
426
|
+
* Floor the Host schema also enforces: the daily benefit window opens at 10:00,
|
|
427
|
+
* so an earlier moment can never claim anything. Mirrored here for immediate UI
|
|
428
|
+
* feedback, the same way `POLL_MIN_MS` mirrors the poll floor.
|
|
429
|
+
*/
|
|
430
|
+
const EARLIEST_CHECK_IN_MINUTE = 600;
|
|
425
431
|
/** Minutes past midnight (UTC+8), split for the two number fields. */
|
|
426
432
|
function splitMinutes(minutes) {
|
|
427
433
|
const safe = Number.isFinite(minutes) ? Math.trunc(minutes) : CHECK_IN_MINUTE_DEFAULT;
|
|
428
|
-
const clamped = safe <
|
|
434
|
+
const clamped = safe < EARLIEST_CHECK_IN_MINUTE || safe > 1439 ? CHECK_IN_MINUTE_DEFAULT : safe;
|
|
429
435
|
return {
|
|
430
436
|
hours: Math.floor(clamped / 60),
|
|
431
437
|
minutes: clamped % 60
|
|
@@ -555,10 +561,11 @@ window.__ModuleLoader__.load({
|
|
|
555
561
|
const parsedMinutes = Number.parseInt(minuteDraft, 10);
|
|
556
562
|
const hours = Number.isFinite(parsedHours) ? Math.min(23, Math.max(0, parsedHours)) : split.hours;
|
|
557
563
|
const minutes = Number.isFinite(parsedMinutes) ? Math.min(59, Math.max(0, parsedMinutes)) : split.minutes;
|
|
558
|
-
const next = hours * 60 + minutes;
|
|
564
|
+
const next = Math.max(EARLIEST_CHECK_IN_MINUTE, hours * 60 + minutes);
|
|
559
565
|
if (next === value) {
|
|
560
|
-
|
|
561
|
-
|
|
566
|
+
const settled = splitMinutes(next);
|
|
567
|
+
setHourDraft(String(settled.hours));
|
|
568
|
+
setMinuteDraft(String(settled.minutes).padStart(2, "0"));
|
|
562
569
|
return;
|
|
563
570
|
}
|
|
564
571
|
onPick(next);
|
|
@@ -3957,7 +3964,7 @@ window.__ModuleLoader__.load({
|
|
|
3957
3964
|
autoCheckInHintGlobal: "Automatically check in daily to claim available perks for the Global account.",
|
|
3958
3965
|
checkInTimeCN: "Check-in time (China)",
|
|
3959
3966
|
checkInTimeGlobal: "Check-in time (Global)",
|
|
3960
|
-
checkInTimeHint: "The moment the automatic check-in runs each day, in UTC+8.",
|
|
3967
|
+
checkInTimeHint: "The moment the automatic check-in runs each day, in UTC+8. Earliest 10:00 — that is when the daily benefit window opens.",
|
|
3961
3968
|
autoCheckInStatusClaimed: "Checked in today (+{amount} Credits)",
|
|
3962
3969
|
autoCheckInStatusAlready: "Already checked in today",
|
|
3963
3970
|
autoCheckInStatusNoCampaign: "No active benefit campaign today",
|
|
@@ -4128,7 +4135,7 @@ window.__ModuleLoader__.load({
|
|
|
4128
4135
|
autoCheckInHintGlobal: "每天自动签到,为国际版账号领取可用福利额度。",
|
|
4129
4136
|
checkInTimeCN: "签到时间(国内版)",
|
|
4130
4137
|
checkInTimeGlobal: "签到时间(国际版)",
|
|
4131
|
-
checkInTimeHint: "每天执行自动签到的时刻,按北京时间(UTC+8
|
|
4138
|
+
checkInTimeHint: "每天执行自动签到的时刻,按北京时间(UTC+8)。最早 10:00,每日福利窗口在此时开启。",
|
|
4132
4139
|
autoCheckInStatusClaimed: "今日已自动签到(+{amount} Credits)",
|
|
4133
4140
|
autoCheckInStatusAlready: "今日已完成签到",
|
|
4134
4141
|
autoCheckInStatusNoCampaign: "今日无可用签到福利活动",
|
package/lib/index.d.ts
CHANGED
|
@@ -773,8 +773,15 @@ interface QoderTransport {
|
|
|
773
773
|
*/
|
|
774
774
|
type QoderEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
775
775
|
/** How the shim should present an upstream failure to its OpenAI client. */
|
|
776
|
-
type UpstreamErrorKind = 'missing_credential' | 'auth' | 'soft_rate' | 'quota_exceeded' | 'server' | 'client';
|
|
777
|
-
/**
|
|
776
|
+
type UpstreamErrorKind = 'missing_credential' | 'auth' | 'soft_rate' | 'quota_exceeded' | 'timeout' | 'server' | 'client';
|
|
777
|
+
/**
|
|
778
|
+
* HTTP status the shim answers each failure kind with.
|
|
779
|
+
*
|
|
780
|
+
* `timeout` is 408 and deliberately not 504: the harness's pi-ai classifier tests
|
|
781
|
+
* `/\b5\d\d\b/` *before* its `timeout` test, so a 5xx status would be read as
|
|
782
|
+
* SERVER no matter what the message says. 408 is also the honest answer for a
|
|
783
|
+
* request the upstream never completed.
|
|
784
|
+
*/
|
|
778
785
|
declare const KIND_STATUS: Readonly<Record<UpstreamErrorKind, number>>;
|
|
779
786
|
/** The chat-completions answer: a streaming `Response`, or a classified failure. */
|
|
780
787
|
type QoderChatResult = {
|
|
@@ -840,6 +847,12 @@ declare function classifyUpstreamError(status: number, body: string): UpstreamEr
|
|
|
840
847
|
* `code: RATE_LIMIT, status: 403` (body carries 10605/isQueued/
|
|
841
848
|
* retryAfterSeconds), and letting the status arm claim it reported a dead
|
|
842
849
|
* credential — which the host never retries — instead of a throttle it would.
|
|
850
|
+
*
|
|
851
|
+
* `QUOTA` and the request family are tested before the auth family for the same
|
|
852
|
+
* reason: both keep the upstream's 401/403 (see `qoderHttpError`), and the status
|
|
853
|
+
* arm would call a spent daily window or a refused request a credential
|
|
854
|
+
* rejection. `status === 402` is tested before the request arm so that a 402 the
|
|
855
|
+
* transport coded `INVALID_REQUEST` (its 4xx fallback) still reads as a quota.
|
|
843
856
|
*/
|
|
844
857
|
declare function kindFromQoderFailure(failure: {
|
|
845
858
|
code: string;
|
|
@@ -929,13 +942,14 @@ declare class QoderUpstreamClient {
|
|
|
929
942
|
/**
|
|
930
943
|
* One minimal reasoning-effort request, as the probe sweep needs it.
|
|
931
944
|
*
|
|
932
|
-
*
|
|
933
|
-
*
|
|
934
|
-
*
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
*
|
|
945
|
+
* A row that declares efforts is still checked locally, so an unlisted value
|
|
946
|
+
* costs no request. A row that declares none — the only kind detection is
|
|
947
|
+
* offered for — sends the value to the upstream and waits for its answer,
|
|
948
|
+
* because the endpoint is the only authority on spellings the catalog never
|
|
949
|
+
* lists. Either refusal is reported as an attributable 400 with code
|
|
950
|
+
* `UNSUPPORTED_REASONING_EFFORT`, and an accepted value costs exactly one
|
|
951
|
+
* streamed request, which this method ends after the first chunk arrives —
|
|
952
|
+
* see the module docs in `probe.ts`.
|
|
939
953
|
*/
|
|
940
954
|
probeEffort(model: string, effort: string | undefined, signal: AbortSignal): Promise<ProbeAttempt>;
|
|
941
955
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as QoderCatalog, B as QODER_PAT_ENV_GLOBAL, C as getMachineId, D as QoderCatalogStore, E as randomSentinel, F as QODER_PROBE_PATH, G as QODER_DATA_DIR_ENV, H as patTail, I as QODER_SETTINGS_FACE_PATH, J as qoderPluginDataDir, K as QODER_DATA_DIR_NAME, L as QODER_STATUS_PATH, M as QODER_GLOBAL_AUTH_PATH, N as QODER_GLOBAL_PROBE_PATH, O as qoderCatalogPath, P as QODER_GLOBAL_STATUS_PATH, R as QODER_AUTH_FILENAME, S as utc8WallClock, T as probeModel, U as qoderCredentialIdentity, V as QoderCredentialStore, W as qoderOwnAuthPath, Y as qoderStateDir, _ as modelInfoOf, a as QODER_HOST_HEARTBEAT_FILENAME, b as createQoderTransport, c as processStartTimeMs, d as writeHostHeartbeat, f as QODER_CONNECT_VERSION, g as kindFromQoderFailure, h as classifyUpstreamError, i as variantFor, j as QODER_AUTH_PATH, k as FALLBACK_QODER_MODELS, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, n as GLOBAL_VARIANT, o as clearHostHeartbeat, p as KIND_STATUS, q as qoderMachineIdPath, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, v as normalizeCredits, w as PROBE_EFFORT_CANDIDATES, x as formatUtc8Date, y as validateApiKey, z as QODER_PAT_ENV_CN } from "./variants-UX2MxZaz.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -1366,20 +1366,12 @@ var JsonFileCheckInStore = class {
|
|
|
1366
1366
|
} catch {}
|
|
1367
1367
|
}
|
|
1368
1368
|
};
|
|
1369
|
-
/**
|
|
1370
|
-
* Returns the current date in YYYY-MM-DD standardized on UTC+8 (Beijing Time).
|
|
1371
|
-
*/
|
|
1372
|
-
function getUtc8DateString(nowMs = Date.now()) {
|
|
1373
|
-
const d = new Date(nowMs);
|
|
1374
|
-
const utc8 = new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 6e4);
|
|
1375
|
-
return `${utc8.getFullYear()}-${String(utc8.getMonth() + 1).padStart(2, "0")}-${String(utc8.getDate()).padStart(2, "0")}`;
|
|
1376
|
-
}
|
|
1377
|
-
/** Clamp any stored/typed value onto a real minute of the day. */
|
|
1369
|
+
/** Clamp any stored/typed value onto a minute the plugin can actually honour. */
|
|
1378
1370
|
function normalizeCheckInMinute(value) {
|
|
1379
1371
|
if (typeof value !== "number" || !Number.isFinite(value)) return 600;
|
|
1380
1372
|
const whole = Math.trunc(value);
|
|
1381
1373
|
if (whole < 0 || whole > 1439) return 600;
|
|
1382
|
-
return whole;
|
|
1374
|
+
return Math.max(600, whole);
|
|
1383
1375
|
}
|
|
1384
1376
|
/**
|
|
1385
1377
|
* Calculates milliseconds until the next occurrence of `minuteOfDay` (UTC+8).
|
|
@@ -1389,8 +1381,7 @@ function normalizeCheckInMinute(value) {
|
|
|
1389
1381
|
*/
|
|
1390
1382
|
function msUntilNextCheckIn(minuteOfDay, nowMs = Date.now()) {
|
|
1391
1383
|
const minute = normalizeCheckInMinute(minuteOfDay);
|
|
1392
|
-
const
|
|
1393
|
-
const utc8Time = new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 6e4);
|
|
1384
|
+
const utc8Time = utc8WallClock(nowMs);
|
|
1394
1385
|
const targetUtc8 = new Date(utc8Time.getTime());
|
|
1395
1386
|
targetUtc8.setHours(Math.floor(minute / 60), minute % 60, 5, 0);
|
|
1396
1387
|
let diff = targetUtc8.getTime() - utc8Time.getTime();
|
|
@@ -1405,8 +1396,7 @@ function msUntilNextCheckIn(minuteOfDay, nowMs = Date.now()) {
|
|
|
1405
1396
|
*/
|
|
1406
1397
|
function isPastCheckInTime(minuteOfDay, nowMs = Date.now()) {
|
|
1407
1398
|
const minute = normalizeCheckInMinute(minuteOfDay);
|
|
1408
|
-
const
|
|
1409
|
-
const utc8 = new Date(d.getTime() + (d.getTimezoneOffset() + 480) * 6e4);
|
|
1399
|
+
const utc8 = utc8WallClock(nowMs);
|
|
1410
1400
|
return utc8.getHours() * 60 + utc8.getMinutes() >= minute;
|
|
1411
1401
|
}
|
|
1412
1402
|
var CheckInScheduler = class {
|
|
@@ -1502,7 +1492,7 @@ var CheckInScheduler = class {
|
|
|
1502
1492
|
async sweepAll(isCatchUp, only) {
|
|
1503
1493
|
if (this.disposed) return;
|
|
1504
1494
|
const nowMs = this.now();
|
|
1505
|
-
const today =
|
|
1495
|
+
const today = formatUtc8Date(nowMs);
|
|
1506
1496
|
for (const target of this.targets) {
|
|
1507
1497
|
if (only !== void 0 && target.variantId !== only) continue;
|
|
1508
1498
|
if (!this.isEnabled(target.variantId)) continue;
|
|
@@ -1912,8 +1902,14 @@ const AUTO_CHECK_IN_FIELD = volatileField(z.boolean().default(false).description
|
|
|
1912
1902
|
* itself rejects an impossible time: a browser time input yields 0–1439, and
|
|
1913
1903
|
* anything outside that range fails Host validation instead of silently
|
|
1914
1904
|
* scheduling a request at a moment that never arrives.
|
|
1905
|
+
*
|
|
1906
|
+
* The floor is the window's own opening rather than midnight, because a moment
|
|
1907
|
+
* before 10:00 is impossible in the same sense: the upstream has not opened the
|
|
1908
|
+
* day's benefit yet, so the run can only read the *previous* window's status
|
|
1909
|
+
* and settle a day that was never claimed. Refusing the value is the point — it
|
|
1910
|
+
* is not a time this plugin could honour.
|
|
1915
1911
|
*/
|
|
1916
|
-
const CHECK_IN_MINUTE_FIELD = volatileField(z.number().default(600).min(
|
|
1912
|
+
const CHECK_IN_MINUTE_FIELD = volatileField(z.number().default(600).min(600).max(1439).description("每日自动签到的时刻(自 UTC+8 午夜起的分钟数,600 = 10:00;最早 10:00,即活动窗口开启时刻)"));
|
|
1917
1913
|
/**
|
|
1918
1914
|
* Quota poll interval: default 5 minutes, floor 1 minute. The status route
|
|
1919
1915
|
* performs a live upstream billing call per request with no cache, so an
|