dsh-multi-router 0.5.3 → 0.5.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.
- package/CHANGELOG.md +16 -0
- package/README.md +32 -18
- package/lib/accounts.js +2 -2
- package/lib/{chunk-JYVJJ7OX.js → chunk-CT6ZYIAF.js} +4 -4
- package/lib/{chunk-JYVJJ7OX.js.map → chunk-CT6ZYIAF.js.map} +2 -2
- package/lib/{chunk-RPW6DI6O.js → chunk-EGLF4CWD.js} +33 -8
- package/lib/{chunk-RPW6DI6O.js.map → chunk-EGLF4CWD.js.map} +3 -3
- package/lib/{chunk-XA3V2DGT.js → chunk-J3XO7RTF.js} +90 -13
- package/lib/chunk-J3XO7RTF.js.map +7 -0
- package/lib/{chunk-U7J4ABAM.js → chunk-MCHV2R25.js} +5 -2
- package/lib/{chunk-U7J4ABAM.js.map → chunk-MCHV2R25.js.map} +2 -2
- package/lib/{chunk-4H6ZXK75.js → chunk-OFFVP6YN.js} +2 -2
- package/lib/{chunk-EJWYFWIT.js → chunk-PQETKUGT.js} +75 -15
- package/lib/chunk-PQETKUGT.js.map +7 -0
- package/lib/cli.js +4 -4
- package/lib/client.js +570 -197
- package/lib/client.js.map +3 -3
- package/lib/clients.js +3 -3
- package/lib/host.js +24 -10
- package/lib/index.js +24 -10
- package/lib/index.js.map +2 -2
- package/lib/manager.js +5 -5
- package/lib/router.js +2 -2
- package/lib/shim.js +3 -3
- package/package.json +1 -1
- package/router.example.json +1 -1
- package/lib/chunk-EJWYFWIT.js.map +0 -7
- package/lib/chunk-XA3V2DGT.js.map +0 -7
- /package/lib/{chunk-4H6ZXK75.js.map → chunk-OFFVP6YN.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。版本号从 `0.1` 起以功能迭代为主,接口在 `1.0` 前可能变化。
|
|
4
4
|
|
|
5
|
+
## 0.5.5
|
|
6
|
+
|
|
7
|
+
- Token 明细与总览增加模型用时、工具调用用时、首 Token 平均(TTFT)与输出速度(TPS);支持单次展开与筛选内成功调用汇总,TPS 按输出 Token / 解码秒数加权,失败与取消不混入成功性能。DSH 步骤沿用会话统计口径,直接上游请求保留独立计时,工具用时不猜测;旧记录不补算。
|
|
8
|
+
- 使用每次调用的独立关联 ID 匹配 DSH 步骤,隔离并发会话与辅助调用;工具开始 / 结果按 ID 配对。只保存数值和散列后的会话标识,不保存原始会话 ID、标题、文本、工具名称、参数或结果。
|
|
9
|
+
- 账号池默认展示「全部供应商」,同时保留单供应商 / 区域筛选;各行展示供应商,跨平台全选 / 全不选原子保存,不改变自动签到设置。
|
|
10
|
+
- 每 24 小时自动检查用户安装的 GitHub / npm 插件;通过 DSH 全局浮层提示更新,支持稍后处理或查看并手动更新。已处理版本 / 提交持久化去重,重启不重复提醒;检查失败不频繁重试,DSH 内置插件不参与。
|
|
11
|
+
- README 更新为本版合成账号截图,替换历史真实账号界面,并使用 npm 也可访问的绝对图片链接。
|
|
12
|
+
|
|
13
|
+
## 0.5.4
|
|
14
|
+
|
|
15
|
+
- 重整整体面板:简洁页签、统一表格与状态展示;账号池集中展示余额、3 天内临期积分、路由冷却、目录模型与签到,支持搜索、供应商内全选 / 全不选,管理详情收进弹窗,适配 DSH 明暗主题与窄窗口。
|
|
16
|
+
- 签到计划支持每日首次启动或每天定时(北京时间),默认保留 10:05;Qoder 遵循每日领取窗口。已处理账号提前跳过队列,改计划、重启和刷新不重置记录;认证失败等待更新凭证后恢复,晚间仅补查尚未提交的失败一次。
|
|
17
|
+
- 插件更新只展示和检查用户自行安装的插件,包含停用插件;DSH 内置和受保护组件从清单、计数及更新请求中排除。
|
|
18
|
+
- Token 明细默认保留 30 天,支持今日、近 3 日、近 7 日、近 30 日、全部已保留及自定义日期筛选;保留天数仍可自定义 1–365 天。
|
|
19
|
+
- 签到计划随配置迁移;增加去重、跨日、计划变更、原子账号批量操作及面板回归验证。
|
|
20
|
+
|
|
5
21
|
## 0.5.3
|
|
6
22
|
|
|
7
23
|
- 签到面板的手机号打码兼容国际区号及以 0 开头的号码,账号名称和自动签到控件标签都隐藏中间数字。
|
package/README.md
CHANGED
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
一个插件内置 WorkBuddy、Trae、Qoder 的认证、模型发现和聊天传输,提供三家的多账号池及集中路由。运行不依赖原来三个 DSH 插件;三家代码随安装包一起打包。基于 MIT 上游,固定版本见 `upstream-lock.json`,许可见 `THIRD_PARTY_NOTICES.md`。
|
|
8
8
|
|
|
9
|
-
版本:0.5.
|
|
9
|
+
版本:0.5.5。适配 DSH Desktop / Web 0.2.0-rc.2,Node.js ^22.19.0 或 >=24。
|
|
10
10
|
|
|
11
11
|
## 在 DSH 里管理
|
|
12
12
|
|
|
13
|
-
打开 **插件 → dsh-multi-router
|
|
13
|
+
打开 **插件 → dsh-multi-router 的配置页**。默认进入总览,八个页签:
|
|
14
14
|
|
|
15
15
|
- **总览与用量**:今日/累计请求与 Token、近 7 日趋势、最近请求、三家平台余额与刷新时间。平台每分钟查询,面板每 15 秒刷新,可暂停面板自动刷新。
|
|
16
|
-
- **Token 明细**:每次上游调用的时间、渠道、账号、路由、实际模型、输入/输出/总 Token
|
|
17
|
-
-
|
|
16
|
+
- **Token 明细**:每次上游调用的时间、渠道、账号、路由、实际模型、输入/输出/总 Token 和结果;支持今日、近 3 日、近 7 日、近 30 日及自定义日期范围(北京时间自然日),并按账号、路由、结果筛选,每页 50 条。缓存输入和推理输出在平台返回时展示,不重复计入总量。默认滚动保留最近 30 × 24 小时,可设置 1–365 天,并随配置迁移。同时展示模型用时、工具调用用时、首 Token 平均(TTFT)和输出速度(TPS),可展开单次性能。DSH 步骤采用会话统计相同口径;TPS 按有效输出 Token / 解码秒数加权计算。性能汇总只包含成功调用,随当前筛选变化;旧记录缺少的分项显示「—」。
|
|
17
|
+
- **账号与登录**:默认「全部供应商」集中展示所有账号,可按供应商 / 区域筛选、搜索、全选 / 全不选。启动时自动读取本机 WorkBuddy / Trae 已登录账号,每分钟重新检测;Trae 自动识别 CN / Global / SOLO,面板展示检测状态,并提供「重新检测本机登录」。添加账号弹窗也可一键获取;账号 ID 自动生成、显示名自动识别,重复账号会提示。Qoder 使用 PAT,可粘贴或获取本机旧连接器已保存的 PAT。新账号自动获取模型与平台用量。详情弹窗查看身份、认证类型、有效期、刷新能力、套餐与积分包;账号可命名、启停、排序和移出。
|
|
18
|
+
- **积分签到**:按账号设置每日启动签到或定时签到(北京时间),保留每日执行记录、领取意图与设备限制,避免重复请求。
|
|
19
|
+
- **插件更新**:仅检查用户安装的 GitHub / npm 插件,包含停用插件;每 24 小时自动检查,在 DSH 任意页面弹窗提醒新版本。同一更新点击关闭或「稍后处理」后不重复提醒,更高版本 / 新提交再次提醒。可在弹窗查看并手动更新;不自动安装。检查失败不冒充发现新版本,内置组件不检查、不展示。
|
|
18
20
|
- **模型管理**:原始模型默认隐藏,只展示统一路由;提供所有渠道「全部展示」「全部隐藏(仅路由)」,作用于现有和以后发现的模型。可搜索、获取真实模型目录,修改显示名与单模型展示开关,每家独立保存。
|
|
19
21
|
- **路由规则**:Free 免费路由排在首位,显示各账号当前免费模型与目录检查时间,支持立即检查。列表显示路由名称、切换策略和目标;新建/编辑打开二级弹窗。「添加模型预配置」可逐个添加或补齐全部三家预配置,不覆盖已有同 ID 路由。模型可从下拉列表选择,候选 ID 和上下文长度放在高级选项中。
|
|
20
22
|
|
|
@@ -28,17 +30,22 @@
|
|
|
28
30
|
|
|
29
31
|
### 界面预览
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
以下为 **v0.5.5 实际面板 + 合成演示数据**。账号名称已替换,不包含真实手机号、邮箱、身份 ID、设备标识、登录凭证或会话内容;余额、用量和性能均为演示数值。
|
|
34
|
+
|
|
35
|
+
| 总览与用量 | Token 明细与调用性能 |
|
|
32
36
|
| --- | --- |
|
|
33
|
-
| 今日 / 累计 Token
|
|
34
|
-
|  |  |  |
|
|
35
39
|
|
|
36
|
-
|
|
|
40
|
+
| 全部供应商账号池 | 路由规则与免费模型 |
|
|
37
41
|
| --- | --- |
|
|
38
|
-
|
|
|
39
|
-
|  |  |
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
| 每日积分签到 | 全局插件更新提醒 |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| 启动 / 定时计划、按账号开启与状态去重 | GitHub / npm 用户插件、手动更新、同版本提醒去重 |
|
|
48
|
+
|  |  |
|
|
42
49
|
|
|
43
50
|
## 内置渠道与默认路由
|
|
44
51
|
|
|
@@ -85,7 +92,7 @@ Qoder 的 `dfmodel` 在真实目录里显示为 DeepSeek-Flash,未声明具体
|
|
|
85
92
|
|
|
86
93
|
额度只决定下一次请求选哪个账号,不中断正在生成的回答;并发请求、最后一次请求可能使阈值超过,积分结算延迟也可能造成超额。这是轮换阈值,不是上游消费硬上限。
|
|
87
94
|
|
|
88
|
-
统计保存于配置旁的 `usage.json`,0600 权限。仅保存账号/路由元数据、计数、平台额度和保留期限内的逐次调用明细,不保存提示词、回答或认证凭证;明细默认保留最近
|
|
95
|
+
统计保存于配置旁的 `usage.json`,0600 权限。仅保存账号/路由元数据、计数、平台额度和保留期限内的逐次调用明细,不保存提示词、回答或认证凭证;明细默认保留最近 30 天(滚动 720 小时),由 `usageRetentionDays` 设置 1–365 天,不再限定 80 条;缩短期限立即删除超期明细,延长不会恢复已删除记录。累计用量、额度轮换基线、临期冷却不清零,日趋势独立保留最近 31 天。Token 从本版本启用后开始计量,不回填历史会话。三家积分单位分别展示,不汇总为通用余额;平台余额下降可包含其他应用消费、积分包到期或延迟结算,不宣称是精确的单请求扣费。平台查询失败保留最后数据与错误、时间;没有可用积分数据的账号不参加积分阈值轮换。
|
|
89
96
|
|
|
90
97
|
账号获取采用官方客户端已有登录文件的只读解析,不修改客户端登录、不复制参考项目代码;自动签到需要在独立的「积分签到」页按账号开启,默认关闭。手动凭证支持 WorkBuddy / Trae 字段表单、三家凭证 JSON 和 Qoder PAT;凭证格式、区域、Trae 设备身份会校验。已有账号可在详情中「补充 / 更新凭证」,旧副本保留。Qoder 普通网页 Cookie 无法替代 PAT;官方 PAT 指引见 https://docs.qoder.com/cli/authentication 。
|
|
91
98
|
|
|
@@ -93,29 +100,36 @@ Qoder 的 `dfmodel` 在真实目录里显示为 DeepSeek-Flash,未声明具体
|
|
|
93
100
|
|
|
94
101
|
第一次启用插件时会执行 WorkBuddy / WorkBuddy AI 与 Trae CN / Global / SOLO 的本机登录检测,将找到的有效登录保存为独立账号副本。首页会展示「本机账号自动获取」结果,区分已保存、未登录和无法读取,并提供「一键获取并保存」及手动添加入口;无需先逐个点击添加账号。之后每分钟检测新增登录,重复检测不重复添加,已停用和已删除的账号保持用户选择。Qoder 仍需添加 PAT。
|
|
95
102
|
|
|
103
|
+
## 面板布局
|
|
104
|
+
|
|
105
|
+
账号池采用表格集中展示账号登录状态、路由参与、平台余额、3 天内到期积分、模型目录数量、调用用量与今日签到。支持名称搜索、当前供应商全选 / 全不选;勾选即时保存,只影响路由参与,不自动开启签到。账号顺序、登录凭证、额度包与复制导出集中在「管理账号」弹窗。
|
|
106
|
+
|
|
107
|
+
总览、签到记录和插件更新使用一致的表格布局;详细执行规则按需展开,路由继续通过二级弹窗编辑。沿用 DSH 明暗主题,窄窗口的宽表格可在表内横向滚动。
|
|
108
|
+
|
|
96
109
|
## 每日积分签到
|
|
97
110
|
|
|
98
111
|
在配置面板打开 **积分签到**。当前接入 WorkBuddy 国内版、Trae CN 与 Qoder CN / Global 的签到能力:先查询平台是否开放活动、账号与设备是否已领取,再提交领取;活动是否开放和奖励额度以平台响应为准。WorkBuddy AI 与 Trae Global 暂无已核验的签到接口,面板会显示需到官方客户端查看,不猜测接口或奖励。
|
|
99
112
|
|
|
100
|
-
-
|
|
113
|
+
- 每个账号可选择「立即签到」,或「签到全部可用账号」。**每日自动签到默认关闭**,按账号单独开启;已停用账号不参与。可选择「每日启动时签到」或「每天定时签到」,定时时间按北京时间设置,默认保留 10:05。启动模式在当天首次运行时执行,持续运行则新一天自动执行;定时模式错过时间后在下次启动补查。Qoder 最早 10:05 查询当日领取窗口,WorkBuddy / Trae 启动模式不受此窗口限制。20:05 后仅补查确认未提交领取的查询失败或尚未开放窗口,每个账号最多补查一次;DSH 关闭期间不执行。
|
|
101
114
|
- Qoder 先用 PAT 交换临时 Job Token,活动请求使用 `Cosy-ClientType: 10`;仅领取有效期内 `CLAIM_BENEFIT` 且 `CLAIMABLE` 的活动。宣传、锁定或未开放给此账号的活动显示「无活动」,不会误报令牌失效;奖励仅显示领取响应实际返回的金额。TRAE 沿用客户端登录的 Refresh Token 和真实设备参数续期,不依赖短信登录,也不按账号数组位置保存状态。
|
|
102
115
|
- WorkBuddy 国内版沿用已保存的客户端 AT / RT,按 JWT 中的账号身份校验请求;续期后保存新 AT 和轮换的 RT。先查今日签到状态及活动结束时间,再提交一次领取,已签或活动结束时跳过;面板展示返回的连签天数、活动累计积分和本次奖励。网络或 5xx 仅对领取前的只读状态查询最多重试两次(2 秒、4 秒),领取请求不做快速重试。接口契约参考 [WorkBuddy-Daily](https://github.com/L0NE-6/WorkBuddy-Daily/blob/main/workbuddy_daily.py),未采用成长任务、抽奖或客户端活跃模拟。
|
|
116
|
+
- 启动、定时、手动按钮共用持久化记录;改签到计划、刷新面板或重启不重置记录。当天已处理的账号不会重复进入请求队列,令牌失效后等待显式更新凭证再恢复。
|
|
103
117
|
- 请求串行,账号间至少间隔 10 秒。先持久化当天意图再发请求,重复账号副本按平台身份去重,调整账号顺序不影响记录。每天最多提交一次领取;提交后网络超时或结果不明确时不重复提交,请到官方客户端核对。成功、今日已领、无活动和结果不明的账号不会加入晚间重试队列。
|
|
104
118
|
- 保留 Trae 原有设备身份;同一设备当天的领取已使用或结果不明确时,不切到同设备的其他账号继续领取。Qoder 优先复用官方 CLI 的设备身份,没有时使用连接器持久化的单一设备身份,所有账号一致,不随签到轮换。
|
|
105
119
|
- 验证、403 拒绝或限流会暂停该平台签到;在官方客户端处理后,可以点击「恢复后续签到」。恢复不会清除今日尝试记录与设备限制。401 或明确过期且无法续期的凭证单独显示「令牌失效」,请在「账号与登录」更新凭证;替换凭证只解除认证失败记录,已提交或结果不明的领取记录仍保留。普通网络查询失败不会要求重交令牌。不提供更换设备身份、代理或绕过验证码的能力;不能保证不触发平台风控。
|
|
106
120
|
- 面板显示最近一轮汇总、账号结果、补查时间、可核验奖励与自动签到开关;全部已完成或没有待补查账号时不重复汇报。签到页的手机号打码,不输出令牌或上游原始错误;成功后刷新平台余额。记录保存在配置旁的 `checkins.json`,0600 权限,仅保存身份哈希、日期和结果。保留最近 30 天,记录损坏时停止签到,不自动清空后重试。
|
|
107
121
|
- 自动签到设置可随配置导入导出,领取记录与平台暂停状态不迁移。多台设备的本地记录不会互相同步,建议只在一台设备开启自动签到;平台领取状态仍会在提交前查询。
|
|
108
122
|
|
|
109
|
-
##
|
|
123
|
+
## 用户安装插件的更新与安装
|
|
110
124
|
|
|
111
|
-
在本插件配置面板打开
|
|
125
|
+
在本插件配置面板打开 **插件更新**,只管理当前 DSH 配置中由用户自行安装的插件,包括其它插件和已停用插件;DSH 内置及受保护组件不展示、不计数,也不发起更新检查。首次打开自动检查,后续超过 24 小时重新打开时再检查,也可随时点击「检查全部更新」。
|
|
112
126
|
|
|
113
127
|
- **npm 来源**:比较 SemVer,保留 `latest` / `next` 等频道;固定版本和版本范围在用户确认更新后升级到最新正式版本。查询沿用 DSH 的 registry、网络、认证与镜像回退设置。
|
|
114
128
|
- **GitHub 来源**:比较原分支 / 标签的 Git 提交,版本号未变化也能发现更新;安装时使用官方 `pnpm update` 路径刷新原分支,并核验实际锁文件提交,避免 `pnpm add` 复用旧锁文件。支持 `github:用户/仓库`、GitHub HTTPS / SSH 地址及锁文件中的提交;固定 Git 提交不自动解除。GitHub 查询需要能够访问仓库公开元数据,私有仓库或限流会显示原因,不会猜测最新提交。
|
|
115
129
|
- 可按名称、来源和可更新状态筛选;支持单个更新、勾选批量更新、取消以及失败后重试。更新前显示目标,并保留每个插件原有启用 / 停用状态。
|
|
116
130
|
- 可直接输入 npm 包名 / 指定版本,或 `github:用户/仓库` 安装新插件。实际安装由 DSH 官方插件管理服务执行兼容检查、镜像重试、配置锁及失败回滚;依赖构建脚本仍在 DSH 原安装页授权,不自动绕过。
|
|
117
131
|
- 结果显示实际安装是否成功及是否需要重启。npm 镜像尚未同步、网络超时、版本不兼容会明确提示。GitHub 下载不会由 npm 镜像代替。
|
|
118
|
-
-
|
|
132
|
+
- 本地文件 / 开发链接标记为原方式更新。不同 DSH 配置之间相互独立,只操作当前配置。
|
|
119
133
|
|
|
120
134
|
## 安装
|
|
121
135
|
|
|
@@ -137,7 +151,7 @@ dsh plugin --profile desktop add dsh-multi-router
|
|
|
137
151
|
dsh plugin --profile web add dsh-multi-router
|
|
138
152
|
```
|
|
139
153
|
|
|
140
|
-
包地址:[dsh-multi-router · npm](https://www.npmjs.com/package/dsh-multi-router)。正式版本由 GitHub Release 自动测试、打包并发布到 npm `latest`,包中已包含编译产物,用户无需自行构建。需要固定版本时可安装 `dsh-multi-router@0.5.
|
|
154
|
+
包地址:[dsh-multi-router · npm](https://www.npmjs.com/package/dsh-multi-router)。正式版本由 GitHub Release 自动测试、打包并发布到 npm `latest`,包中已包含编译产物,用户无需自行构建。需要固定版本时可安装 `dsh-multi-router@0.5.5`。
|
|
141
155
|
|
|
142
156
|
已使用 npm 来源的用户更新:
|
|
143
157
|
|
|
@@ -194,7 +208,7 @@ cd dsh-multi-router
|
|
|
194
208
|
npm ci
|
|
195
209
|
npm run check
|
|
196
210
|
npm run pack:release
|
|
197
|
-
dsh plugin --profile desktop add ./dsh-multi-router-0.5.
|
|
211
|
+
dsh plugin --profile desktop add ./dsh-multi-router-0.5.5.tgz
|
|
198
212
|
```
|
|
199
213
|
|
|
200
214
|
### 配置账号
|
package/lib/accounts.js
CHANGED
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
importAccount,
|
|
4
4
|
localAccountKey,
|
|
5
5
|
readPortableCredential
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-OFFVP6YN.js";
|
|
7
7
|
import "./chunk-FTM7RHQV.js";
|
|
8
8
|
import "./chunk-S5KMFAQM.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-MCHV2R25.js";
|
|
10
10
|
import "./chunk-TEZ2FMEO.js";
|
|
11
11
|
export {
|
|
12
12
|
discoverLocalAccounts,
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
} from "./chunk-DNQPZMPF.js";
|
|
5
5
|
import {
|
|
6
6
|
RouteError
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-J3XO7RTF.js";
|
|
8
8
|
import {
|
|
9
9
|
REASONING_EFFORTS
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MCHV2R25.js";
|
|
11
11
|
|
|
12
12
|
// src/shim.mjs
|
|
13
13
|
import { createServer } from "node:http";
|
|
@@ -70,7 +70,7 @@ function createShim(router, { modelPrefix = "" } = {}) {
|
|
|
70
70
|
if (effort !== "default" && !REASONING_EFFORTS.includes(effort)) throw new RouteError(400, "invalid_reasoning_effort");
|
|
71
71
|
input.reasoning_effort = effort === "default" ? null : effort;
|
|
72
72
|
}
|
|
73
|
-
const selected = await router.chat({ ...input, model: modelPrefix + input.model }, signal);
|
|
73
|
+
const selected = await router.chat({ ...input, model: modelPrefix + input.model }, signal, { callId: typeof req.headers["x-multi-router-call"] === "string" ? req.headers["x-multi-router-call"] : void 0, session: typeof req.headers["x-multi-router-session"] === "string" ? req.headers["x-multi-router-session"] : void 0 });
|
|
74
74
|
signal.throwIfAborted();
|
|
75
75
|
res.writeHead(200, {
|
|
76
76
|
"Content-Type": "text/event-stream; charset=utf-8",
|
|
@@ -113,4 +113,4 @@ function createShim(router, { modelPrefix = "" } = {}) {
|
|
|
113
113
|
export {
|
|
114
114
|
createShim
|
|
115
115
|
};
|
|
116
|
-
//# sourceMappingURL=chunk-
|
|
116
|
+
//# sourceMappingURL=chunk-CT6ZYIAF.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/shim.mjs"],
|
|
4
|
-
"sourcesContent": ["import { createServer } from 'node:http';\nimport { randomBytes, timingSafeEqual } from 'node:crypto';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { hostIsLoopback, originIsLoopback } from '../upstream/workbuddy/src/loopback.ts';\nimport { RouteError } from './router.mjs';\nimport { REASONING_EFFORTS } from './reasoning.mjs';\n\nexport function createShim(router, { modelPrefix = '' } = {}) {\n const secret = randomBytes(32).toString('base64url');\n const server = createServer((req, res) => { void handle(req, res); });\n const ready = new Promise((resolve, reject) => {\n server.once('listening', resolve); server.once('error', reject);\n });\n server.listen(0, '127.0.0.1');\n const json = (res, status, value) => {\n res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(value));\n };\n async function handle(req, res) {\n const controller = new AbortController();\n req.on('aborted', () => controller.abort());\n res.on('close', () => { if (!res.writableEnded) controller.abort(); });\n const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(router.config.requestTimeoutMs)]);\n try {\n if (!hostIsLoopback(req.headers.host) || !originIsLoopback(req.headers.origin)) throw new RouteError(403, 'loopback_required');\n const bearer = Buffer.from((req.headers.authorization ?? '').replace(/^Bearer\\s+/i, ''));\n const expected = Buffer.from(secret);\n if (bearer.length !== expected.length || !timingSafeEqual(bearer, expected)) throw new RouteError(401, 'unauthorized');\n if (req.method === 'GET' && req.url === '/router/status') { json(res, 200, router.status()); return; }\n if (req.method === 'GET' && req.url === '/v1/models') {\n json(res, 200, { object: 'list', data: Object.keys(router.config.routes).filter(id => modelPrefix ? id.startsWith(modelPrefix) : !id.includes('::'))\n .map(id => ({ id: modelPrefix ? id.slice(modelPrefix.length) : id, object: 'model', created: 0, owned_by: 'multi-router' })) }); return;\n }\n if (req.method !== 'POST' || req.url !== '/v1/chat/completions') throw new RouteError(404, 'not_found');\n if (!/^application\\/json(?:\\s*;|$)/i.test(req.headers['content-type'] ?? '')) throw new RouteError(415, 'json_required');\n let size = 0; const chunks = [];\n for await (const chunk of req) {\n size += chunk.length;\n if (size > 32 * 1024 * 1024) throw new RouteError(413, 'request_too_large');\n chunks.push(chunk); signal.throwIfAborted();\n }\n let input;\n try { input = JSON.parse(Buffer.concat(chunks).toString('utf8')); }\n catch { throw new RouteError(400, 'invalid_json'); }\n if (!input || typeof input !== 'object' || Array.isArray(input)) throw new RouteError(400, 'invalid_request');\n const effort = req.headers['x-multi-router-reasoning'];\n if (effort !== undefined) {\n if (effort !== 'default' && !REASONING_EFFORTS.includes(effort)) throw new RouteError(400, 'invalid_reasoning_effort');\n input.reasoning_effort = effort === 'default' ? null : effort;\n }\n const selected = await router.chat({ ...input, model: modelPrefix + input.model }, signal);\n signal.throwIfAborted();\n res.writeHead(200, { 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-cache',\n 'X-Accel-Buffering': 'no', 'X-Router-Account': selected.account, 'X-Router-Provider': selected.provider });\n await pipeline(Readable.fromWeb(selected.response.body), res, { signal });\n } catch (error) {\n if (res.destroyed) return;\n if (res.headersSent) { res.destroy(); return; }\n const status = error instanceof RouteError ? error.status : signal.aborted ? 504 : 500;\n json(res, status, { error: { type: error instanceof RouteError ? error.code : signal.aborted ? 'request_timeout' : 'internal_error',\n message: error instanceof RouteError ? error.message : 'Router request failed',\n hint: error instanceof RouteError ? error.hint : undefined,\n attempts: error instanceof RouteError ? error.attempts : [] } });\n }\n }\n return { ready, token: () => secret, baseUrl: () => `http://127.0.0.1:${server.address().port}`,\n close: async () => { await ready.catch(() => {}); await new Promise(resolve => { server.close(resolve); server.closeAllConnections(); }); } };\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { createServer } from 'node:http';\nimport { randomBytes, timingSafeEqual } from 'node:crypto';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { hostIsLoopback, originIsLoopback } from '../upstream/workbuddy/src/loopback.ts';\nimport { RouteError } from './router.mjs';\nimport { REASONING_EFFORTS } from './reasoning.mjs';\n\nexport function createShim(router, { modelPrefix = '' } = {}) {\n const secret = randomBytes(32).toString('base64url');\n const server = createServer((req, res) => { void handle(req, res); });\n const ready = new Promise((resolve, reject) => {\n server.once('listening', resolve); server.once('error', reject);\n });\n server.listen(0, '127.0.0.1');\n const json = (res, status, value) => {\n res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(value));\n };\n async function handle(req, res) {\n const controller = new AbortController();\n req.on('aborted', () => controller.abort());\n res.on('close', () => { if (!res.writableEnded) controller.abort(); });\n const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(router.config.requestTimeoutMs)]);\n try {\n if (!hostIsLoopback(req.headers.host) || !originIsLoopback(req.headers.origin)) throw new RouteError(403, 'loopback_required');\n const bearer = Buffer.from((req.headers.authorization ?? '').replace(/^Bearer\\s+/i, ''));\n const expected = Buffer.from(secret);\n if (bearer.length !== expected.length || !timingSafeEqual(bearer, expected)) throw new RouteError(401, 'unauthorized');\n if (req.method === 'GET' && req.url === '/router/status') { json(res, 200, router.status()); return; }\n if (req.method === 'GET' && req.url === '/v1/models') {\n json(res, 200, { object: 'list', data: Object.keys(router.config.routes).filter(id => modelPrefix ? id.startsWith(modelPrefix) : !id.includes('::'))\n .map(id => ({ id: modelPrefix ? id.slice(modelPrefix.length) : id, object: 'model', created: 0, owned_by: 'multi-router' })) }); return;\n }\n if (req.method !== 'POST' || req.url !== '/v1/chat/completions') throw new RouteError(404, 'not_found');\n if (!/^application\\/json(?:\\s*;|$)/i.test(req.headers['content-type'] ?? '')) throw new RouteError(415, 'json_required');\n let size = 0; const chunks = [];\n for await (const chunk of req) {\n size += chunk.length;\n if (size > 32 * 1024 * 1024) throw new RouteError(413, 'request_too_large');\n chunks.push(chunk); signal.throwIfAborted();\n }\n let input;\n try { input = JSON.parse(Buffer.concat(chunks).toString('utf8')); }\n catch { throw new RouteError(400, 'invalid_json'); }\n if (!input || typeof input !== 'object' || Array.isArray(input)) throw new RouteError(400, 'invalid_request');\n const effort = req.headers['x-multi-router-reasoning'];\n if (effort !== undefined) {\n if (effort !== 'default' && !REASONING_EFFORTS.includes(effort)) throw new RouteError(400, 'invalid_reasoning_effort');\n input.reasoning_effort = effort === 'default' ? null : effort;\n }\n const selected = await router.chat({ ...input, model: modelPrefix + input.model }, signal, { callId: typeof req.headers['x-multi-router-call'] === 'string' ? req.headers['x-multi-router-call'] : undefined, session: typeof req.headers['x-multi-router-session'] === 'string' ? req.headers['x-multi-router-session'] : undefined });\n signal.throwIfAborted();\n res.writeHead(200, { 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-cache',\n 'X-Accel-Buffering': 'no', 'X-Router-Account': selected.account, 'X-Router-Provider': selected.provider });\n await pipeline(Readable.fromWeb(selected.response.body), res, { signal });\n } catch (error) {\n if (res.destroyed) return;\n if (res.headersSent) { res.destroy(); return; }\n const status = error instanceof RouteError ? error.status : signal.aborted ? 504 : 500;\n json(res, status, { error: { type: error instanceof RouteError ? error.code : signal.aborted ? 'request_timeout' : 'internal_error',\n message: error instanceof RouteError ? error.message : 'Router request failed',\n hint: error instanceof RouteError ? error.hint : undefined,\n attempts: error instanceof RouteError ? error.attempts : [] } });\n }\n }\n return { ready, token: () => secret, baseUrl: () => `http://127.0.0.1:${server.address().port}`,\n close: async () => { await ready.catch(() => {}); await new Promise(resolve => { server.close(resolve); server.closeAllConnections(); }); } };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;AAAA,SAAS,oBAAoB;AAC7B,SAAS,aAAa,uBAAuB;AAC7C,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AAKlB,SAAS,WAAW,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC,GAAG;AAC5D,QAAM,SAAS,YAAY,EAAE,EAAE,SAAS,WAAW;AACnD,QAAM,SAAS,aAAa,CAAC,KAAK,QAAQ;AAAE,SAAK,OAAO,KAAK,GAAG;AAAA,EAAG,CAAC;AACpE,QAAM,QAAQ,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC7C,WAAO,KAAK,aAAa,OAAO;AAAG,WAAO,KAAK,SAAS,MAAM;AAAA,EAChE,CAAC;AACD,SAAO,OAAO,GAAG,WAAW;AAC5B,QAAM,OAAO,CAAC,KAAK,QAAQ,UAAU;AACnC,QAAI,UAAU,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AAAG,QAAI,IAAI,KAAK,UAAU,KAAK,CAAC;AAAA,EAC9F;AACA,iBAAe,OAAO,KAAK,KAAK;AAC9B,UAAM,aAAa,IAAI,gBAAgB;AACvC,QAAI,GAAG,WAAW,MAAM,WAAW,MAAM,CAAC;AAC1C,QAAI,GAAG,SAAS,MAAM;AAAE,UAAI,CAAC,IAAI,cAAe,YAAW,MAAM;AAAA,IAAG,CAAC;AACrE,UAAM,SAAS,YAAY,IAAI,CAAC,WAAW,QAAQ,YAAY,QAAQ,OAAO,OAAO,gBAAgB,CAAC,CAAC;AACvG,QAAI;AACF,UAAI,CAAC,eAAe,IAAI,QAAQ,IAAI,KAAK,CAAC,iBAAiB,IAAI,QAAQ,MAAM,EAAG,OAAM,IAAI,WAAW,KAAK,mBAAmB;AAC7H,YAAM,SAAS,OAAO,MAAM,IAAI,QAAQ,iBAAiB,IAAI,QAAQ,eAAe,EAAE,CAAC;AACvF,YAAM,WAAW,OAAO,KAAK,MAAM;AACnC,UAAI,OAAO,WAAW,SAAS,UAAU,CAAC,gBAAgB,QAAQ,QAAQ,EAAG,OAAM,IAAI,WAAW,KAAK,cAAc;AACrH,UAAI,IAAI,WAAW,SAAS,IAAI,QAAQ,kBAAkB;AAAE,aAAK,KAAK,KAAK,OAAO,OAAO,CAAC;AAAG;AAAA,MAAQ;AACrG,UAAI,IAAI,WAAW,SAAS,IAAI,QAAQ,cAAc;AACpD,aAAK,KAAK,KAAK,EAAE,QAAQ,QAAQ,MAAM,OAAO,KAAK,OAAO,OAAO,MAAM,EAAE,OAAO,QAAM,cAAc,GAAG,WAAW,WAAW,IAAI,CAAC,GAAG,SAAS,IAAI,CAAC,EAChJ,IAAI,SAAO,EAAE,IAAI,cAAc,GAAG,MAAM,YAAY,MAAM,IAAI,IAAI,QAAQ,SAAS,SAAS,GAAG,UAAU,eAAe,EAAE,EAAE,CAAC;AAAG;AAAA,MACrI;AACA,UAAI,IAAI,WAAW,UAAU,IAAI,QAAQ,uBAAwB,OAAM,IAAI,WAAW,KAAK,WAAW;AACtG,UAAI,CAAC,gCAAgC,KAAK,IAAI,QAAQ,cAAc,KAAK,EAAE,EAAG,OAAM,IAAI,WAAW,KAAK,eAAe;AACvH,UAAI,OAAO;AAAG,YAAM,SAAS,CAAC;AAC9B,uBAAiB,SAAS,KAAK;AAC7B,gBAAQ,MAAM;AACd,YAAI,OAAO,KAAK,OAAO,KAAM,OAAM,IAAI,WAAW,KAAK,mBAAmB;AAC1E,eAAO,KAAK,KAAK;AAAG,eAAO,eAAe;AAAA,MAC5C;AACA,UAAI;AACJ,UAAI;AAAE,gBAAQ,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,CAAC;AAAA,MAAG,QAC5D;AAAE,cAAM,IAAI,WAAW,KAAK,cAAc;AAAA,MAAG;AACnD,UAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,OAAM,IAAI,WAAW,KAAK,iBAAiB;AAC5G,YAAM,SAAS,IAAI,QAAQ,0BAA0B;AACrD,UAAI,WAAW,QAAW;AACxB,YAAI,WAAW,aAAa,CAAC,kBAAkB,SAAS,MAAM,EAAG,OAAM,IAAI,WAAW,KAAK,0BAA0B;AACrH,cAAM,mBAAmB,WAAW,YAAY,OAAO;AAAA,MACzD;AACA,YAAM,WAAW,MAAM,OAAO,KAAK,EAAE,GAAG,OAAO,OAAO,cAAc,MAAM,MAAM,GAAG,QAAQ,EAAE,QAAQ,OAAO,IAAI,QAAQ,qBAAqB,MAAM,WAAW,IAAI,QAAQ,qBAAqB,IAAI,QAAW,SAAS,OAAO,IAAI,QAAQ,wBAAwB,MAAM,WAAW,IAAI,QAAQ,wBAAwB,IAAI,OAAU,CAAC;AACtU,aAAO,eAAe;AACtB,UAAI,UAAU,KAAK;AAAA,QAAE,gBAAgB;AAAA,QAAoC,iBAAiB;AAAA,QACxF,qBAAqB;AAAA,QAAM,oBAAoB,SAAS;AAAA,QAAS,qBAAqB,SAAS;AAAA,MAAS,CAAC;AAC3G,YAAM,SAAS,SAAS,QAAQ,SAAS,SAAS,IAAI,GAAG,KAAK,EAAE,OAAO,CAAC;AAAA,IAC1E,SAAS,OAAO;AACd,UAAI,IAAI,UAAW;AACnB,UAAI,IAAI,aAAa;AAAE,YAAI,QAAQ;AAAG;AAAA,MAAQ;AAC9C,YAAM,SAAS,iBAAiB,aAAa,MAAM,SAAS,OAAO,UAAU,MAAM;AACnF,WAAK,KAAK,QAAQ,EAAE,OAAO;AAAA,QAAE,MAAM,iBAAiB,aAAa,MAAM,OAAO,OAAO,UAAU,oBAAoB;AAAA,QACjH,SAAS,iBAAiB,aAAa,MAAM,UAAU;AAAA,QACvD,MAAM,iBAAiB,aAAa,MAAM,OAAO;AAAA,QACjD,UAAU,iBAAiB,aAAa,MAAM,WAAW,CAAC;AAAA,MAAE,EAAE,CAAC;AAAA,IACnE;AAAA,EACF;AACA,SAAO;AAAA,IAAE;AAAA,IAAO,OAAO,MAAM;AAAA,IAAQ,SAAS,MAAM,oBAAoB,OAAO,QAAQ,EAAE,IAAI;AAAA,IAC3F,OAAO,YAAY;AAAE,YAAM,MAAM,MAAM,MAAM;AAAA,MAAC,CAAC;AAAG,YAAM,IAAI,QAAQ,aAAW;AAAE,eAAO,MAAM,OAAO;AAAG,eAAO,oBAAoB;AAAA,MAAG,CAAC;AAAA,IAAG;AAAA,EAAE;AAChJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -12,11 +12,11 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
RouteError,
|
|
14
14
|
abortable
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-J3XO7RTF.js";
|
|
16
16
|
import {
|
|
17
17
|
beijingDay,
|
|
18
18
|
writePrivateJson
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-MCHV2R25.js";
|
|
20
20
|
import {
|
|
21
21
|
WorkBuddyUpstreamClient,
|
|
22
22
|
modelWithCurrentPromotion,
|
|
@@ -4801,6 +4801,7 @@ var supportsCheckin = (provider) => ["workbuddy", "trae", "qoder", "qoder-global
|
|
|
4801
4801
|
var hash = (value) => createHash2("sha256").update(value).digest("hex");
|
|
4802
4802
|
var nextDay = (now) => Date.parse(`${beijingDay(now)}T00:00:00+08:00`) + 864e5;
|
|
4803
4803
|
var evening = (now) => Date.parse(`${beijingDay(now)}T20:05:00+08:00`);
|
|
4804
|
+
var windowAt = (a, now) => Date.parse(`${beijingDay(now)}T${a.provider.startsWith("qoder") ? "10:05" : "00:00"}:00+08:00`);
|
|
4804
4805
|
function checkinFailure(error, submitted = false) {
|
|
4805
4806
|
const status = error?.status ?? error?.failure?.status ?? Number(/\bhttp\s+(401|403|429)\b/i.exec(error?.message || "")?.[1]);
|
|
4806
4807
|
if (status === 401) return { status: "auth-invalid" };
|
|
@@ -4848,21 +4849,43 @@ var Checkins = class {
|
|
|
4848
4849
|
this.failed = true;
|
|
4849
4850
|
}
|
|
4850
4851
|
}
|
|
4852
|
+
get settings() {
|
|
4853
|
+
return this.manager.config.checkinSchedule ?? { mode: "scheduled", time: "10:05" };
|
|
4854
|
+
}
|
|
4855
|
+
last(account) {
|
|
4856
|
+
return Object.values(this.data.records).filter((r) => r.ids?.includes(account.id)).sort((a, b) => b.at - a.at)[0];
|
|
4857
|
+
}
|
|
4858
|
+
ready(account, now = this.now()) {
|
|
4859
|
+
const last = this.last(account);
|
|
4860
|
+
return account.enabled !== false && supportsCheckin(account.provider) && !this.data.pauses[account.provider] && last?.status !== "auth-invalid" && now >= windowAt(account, now) && (last?.day !== beijingDay(now) || canRetry(last, now));
|
|
4861
|
+
}
|
|
4862
|
+
dueAt(account, now = this.now()) {
|
|
4863
|
+
const scheduled = this.settings.mode === "startup" ? "00:00" : this.settings.time;
|
|
4864
|
+
return Math.max(windowAt(account, now), Date.parse(`${beijingDay(now)}T${scheduled}:00+08:00`));
|
|
4865
|
+
}
|
|
4866
|
+
tick() {
|
|
4867
|
+
if (this.failed || this.flight || this.abort.signal.aborted) return;
|
|
4868
|
+
const now = this.now();
|
|
4869
|
+
const ids = this.manager.config.accounts.filter((a) => a.autoCheckin === true && this.ready(a, now) && now >= this.dueAt(a, now)).map((a) => a.id);
|
|
4870
|
+
if (ids.length) this.start(ids, true);
|
|
4871
|
+
}
|
|
4851
4872
|
state() {
|
|
4852
4873
|
return {
|
|
4853
4874
|
running: !!this.flight,
|
|
4854
4875
|
current: this.current,
|
|
4855
4876
|
error: this.failed ? "\u7B7E\u5230\u8BB0\u5F55\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u5DF2\u505C\u6B62\u7B7E\u5230\uFF1B\u8BF7\u4FEE\u590D\u8BB0\u5F55\u6587\u4EF6\u540E\u91CD\u542F\u63D2\u4EF6\u3002" : void 0,
|
|
4856
|
-
|
|
4877
|
+
settings: this.settings,
|
|
4878
|
+
schedule: `${this.settings.mode === "startup" ? "\u6BCF\u65E5\u9996\u6B21\u542F\u52A8\u65F6\u7B7E\u5230\uFF0C\u6301\u7EED\u8FD0\u884C\u5219\u65B0\u4E00\u5929\u81EA\u52A8\u6267\u884C" : `\u5317\u4EAC\u65F6\u95F4\u6BCF\u5929 ${this.settings.time} \u5B9A\u65F6\u7B7E\u5230\uFF0C\u9519\u8FC7\u65F6\u542F\u52A8\u8865\u67E5`}\uFF1B20:05 \u4EC5\u8865\u67E5\u672A\u63D0\u4EA4\u9886\u53D6\u7684\u5931\u8D25`,
|
|
4857
4879
|
report: this.data.report,
|
|
4858
4880
|
accounts: this.manager.config.accounts.map((a) => {
|
|
4859
|
-
const
|
|
4860
|
-
const last = rows.sort((a2, b) => b.at - a2.at)[0];
|
|
4881
|
+
const last = this.last(a);
|
|
4861
4882
|
const pause = this.data.pauses[a.provider];
|
|
4862
4883
|
return {
|
|
4863
4884
|
id: a.id,
|
|
4864
4885
|
supported: supportsCheckin(a.provider),
|
|
4865
4886
|
auto: a.autoCheckin === true,
|
|
4887
|
+
ready: this.ready(a),
|
|
4888
|
+
scheduledAt: this.dueAt(a),
|
|
4866
4889
|
paused: !!pause,
|
|
4867
4890
|
last: last ? {
|
|
4868
4891
|
day: last.day,
|
|
@@ -4881,7 +4904,7 @@ var Checkins = class {
|
|
|
4881
4904
|
start(ids, automatic = false) {
|
|
4882
4905
|
if (this.failed || this.abort.signal.aborted || this.flight) return;
|
|
4883
4906
|
if (!automatic && (!Array.isArray(ids) || !ids.length || ids.length > 200 || ids.some((id) => !this.manager.config.accounts.some((a) => a.id === id)))) throw Error("\u8BF7\u9009\u62E9\u6709\u6548\u7684\u7B7E\u5230\u8D26\u53F7");
|
|
4884
|
-
const selected = this.manager.config.accounts.filter((a) => supportsCheckin(a.provider) && a.enabled !== false && (automatic ? a.autoCheckin === true : ids.includes(a.id))).map((a) => a.id);
|
|
4907
|
+
const selected = this.manager.config.accounts.filter((a) => supportsCheckin(a.provider) && a.enabled !== false && (automatic ? a.autoCheckin === true && (!ids || ids.includes(a.id)) : ids.includes(a.id)) && this.ready(a) && (!automatic || this.now() >= this.dueAt(a))).map((a) => a.id);
|
|
4885
4908
|
if (!selected.length) return;
|
|
4886
4909
|
this.flight = (async () => {
|
|
4887
4910
|
let requested = false;
|
|
@@ -4930,10 +4953,11 @@ var Checkins = class {
|
|
|
4930
4953
|
}
|
|
4931
4954
|
async run(account, automatic = false) {
|
|
4932
4955
|
const now = this.now(), day = beijingDay(now);
|
|
4933
|
-
if (now <
|
|
4956
|
+
if (now < windowAt(account, now) || automatic && now < this.dueAt(account, now)) return false;
|
|
4934
4957
|
return withFileLock(this.file, async () => {
|
|
4935
4958
|
await this.read();
|
|
4936
4959
|
if (this.abort.signal.aborted || this.data.pauses[account.provider]) return false;
|
|
4960
|
+
if (this.last(account)?.status === "auth-invalid") return false;
|
|
4937
4961
|
const previousEntry = Object.entries(this.data.records).find(([, r]) => r.day === day && r.ids?.includes(account.id));
|
|
4938
4962
|
const previous = previousEntry?.[1];
|
|
4939
4963
|
if (previous && !canRetry(previous, now)) return false;
|
|
@@ -4960,6 +4984,7 @@ var Checkins = class {
|
|
|
4960
4984
|
if (!identity?.account || account.provider === "trae" && !identity.device) throw Error("Missing check-in identity");
|
|
4961
4985
|
} catch (error) {
|
|
4962
4986
|
pending.status = checkinFailure(error).status;
|
|
4987
|
+
if (pending.status !== "retryable") delete pending.retryAt;
|
|
4963
4988
|
if (pending.status === "paused") this.data.pauses[account.provider] = { at: now };
|
|
4964
4989
|
await writePrivateJson(this.file, this.data);
|
|
4965
4990
|
return true;
|
|
@@ -5476,4 +5501,4 @@ export {
|
|
|
5476
5501
|
refreshWorkBuddyCheckinCredential,
|
|
5477
5502
|
createWorkBuddyCheckin
|
|
5478
5503
|
};
|
|
5479
|
-
//# sourceMappingURL=chunk-
|
|
5504
|
+
//# sourceMappingURL=chunk-EGLF4CWD.js.map
|