@eddyskywalker/dsh-chatgpt-subscription 0.36.0 → 0.36.1
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 +9 -0
- package/lib/client.js +73 -12
- package/lib/client.js.map +1 -1
- package/lib/index.js +93 -45
- package/lib/types/client/cline-pass/ClinePassComposerQuota.d.ts +5 -2
- package/lib/types/client/cline-pass/ClinePassComposerQuota.d.ts.map +1 -1
- package/lib/types/client/cline-pass/ClinePassSection.d.ts.map +1 -1
- package/lib/types/client/cline-pass/api.d.ts +21 -1
- package/lib/types/client/cline-pass/api.d.ts.map +1 -1
- package/lib/types/host/cline-pass/routes.d.ts +27 -3
- package/lib/types/host/cline-pass/routes.d.ts.map +1 -1
- package/lib/types/shared/cline-pass-contracts.d.ts +7 -0
- package/lib/types/shared/cline-pass-contracts.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.36.1 - 2026-10-22
|
|
6
|
+
- **[cline-pass] 额度不再只在手动刷新时动:状态读取按 TTL 在答背后刷新,设置卡与输入框徽标 60 秒自动更新**
|
|
7
|
+
- **现象**:Cline Pass 设置页打开后额度数字就定住了——同一次会话里消耗掉的部分,要重新打开页面或点一次「刷新额度」才会反映;输入框旁的额度徽标同样停在首次读到的数字。
|
|
8
|
+
- **根因**:两层各缺一半。宿主侧 `GET /cline-pass/api/status` 只答 `store.usage()` 的内存快照,只有显式 `POST /usage` 才真正向上游读(模块注释还写着「读取不触发上游调用」);客户端侧设置页只在挂载时读一次 status,而共享徽标虽然自带 60 秒轮询,cline 这一行的 `readStatus` 却把 `refresh` 参数丢掉了,点击于是退化成又一次廉价 GET。
|
|
9
|
+
- **改法(宿主)**:新增 `CLINE_PASS_QUOTA_TTL_MS`(5 分钟)与 `usageStale()`(`fetchedAt <= 0` 同样算过期);`GET /status` 先答缓存,快照缺失或过期时用 `QuotaRefresh` 在答背后起一轮刷新,并把 `quotaRefreshing` 放进状态 DTO,让客户端知道该立刻补读;扇出逻辑抽成 `refreshUsage()`,显式 `POST /usage` 与后台刷新共用同一份实现,同源校验、400 文案、凭据零外泄全部保持不变。单飞由 `start()` 的 `pending` 保证:N 次轮询只扇出一轮。另给路由选项加了 `quotaTtlMs` 测试缝。
|
|
10
|
+
- **改法(客户端)**:`ClinePassApi.status(refresh)` 分流——`false` 走 `GET /status`,`true` 走 `POST /usage` 并只取其中的 status 半边;设置页补上 60 秒可见轮询 + `visibilitychange` + `createQuotaFollowUp`(读到 `quotaRefreshing: true` 就 1.5 秒后补读,最多 3 次);轮询是静默的——失败既不刷红字也不清掉用户正在看的红字,半输入的草稿由既有两道闸保护;徽标把 `refresh` 转发出去,点击才真正强制刷新。
|
|
11
|
+
- **测试**:新增 `test/cline-pass-quota-poll.test.ts`(9 例)与 `test/cline-pass-quota-poll.test.tsx`(10 例)——冷缓存即排刷新并回 `quotaRefreshing`、新鲜快照 0 次上游调用、假 Date 拨过 TTL 后缓存照答而背后替换、2 账号 3 次轮询只扇出 2 次、显式动作立即刷新且 400/403 原样、徽标点击走 POST /usage、隐藏标签页不读、轮询失败静默、卸载清理定时器与监听。
|
|
12
|
+
- **验证**:`npm run typecheck` 三工程 0 错误;`npm test` 274 文件 / 4470 例通过(1 条 `minimax-code-video-history` 的 Windows `rename EPERM` 抖动,单跑 22/22 通过,与本改动无关);`npm run build` 通过。
|
|
13
|
+
- **已知边界**:`QuotaRefresh.run()` 不与正在跑的 `start()` 合流,所以「手动刷新正好撞上后台刷新」这一步仍会多扇出一轮;两轮写入形状相同、后写的 `setUsage` 生效,代价是一次多余上游请求。真正合流要给共享 helper 加 `wait`,会牵动另外六条线路,本次不做,代码里按此写明。
|
|
5
14
|
## 0.36.0 - 2026-10-22
|
|
6
15
|
- **[client] 修复俄语语言包与 `@linxin666/dsh-i18n` 等已安装语言包冲突导致插件客户端激活失败崩溃的问题**
|
|
7
16
|
- **现象**:Web 界面启动失败,提示 `web boot: 1 entry did not activate`,`@eddyskywalker/dsh-chatgpt-subscription: failed`。
|
package/lib/client.js
CHANGED
|
@@ -12801,14 +12801,37 @@ window.__ModuleLoader__.load({
|
|
|
12801
12801
|
//#region src/client/cline-pass/api.ts
|
|
12802
12802
|
const api$7 = createLineApi(CLINE_PASS_ROUTE_PREFIX, "Cline Pass");
|
|
12803
12803
|
/**
|
|
12804
|
+
* Whether the host is refreshing the quota behind this answer.
|
|
12805
|
+
*
|
|
12806
|
+
* The field is optional by contract, because a host built before it existed
|
|
12807
|
+
* answers without it. Every caller only ever compares it to `true`, so "absent"
|
|
12808
|
+
* and "false" read the same — nothing is in flight — and a stale host degrades to
|
|
12809
|
+
* plain polling instead of crashing. Kept as one reader so that convention lives
|
|
12810
|
+
* in a single place rather than being re-derived at each call site.
|
|
12811
|
+
*/
|
|
12812
|
+
function quotaRefreshingOf(status) {
|
|
12813
|
+
return status.quotaRefreshing === true;
|
|
12814
|
+
}
|
|
12815
|
+
/**
|
|
12804
12816
|
* The Cline Pass settings API, as the card uses it.
|
|
12805
12817
|
*
|
|
12806
12818
|
* Deliberately the same envelope and the same fetch shape every other provider
|
|
12807
12819
|
* section uses, so the shared account-pool card and this helper read alike.
|
|
12808
12820
|
*/
|
|
12809
12821
|
var ClinePassApi = class {
|
|
12810
|
-
|
|
12811
|
-
|
|
12822
|
+
/**
|
|
12823
|
+
* One status read.
|
|
12824
|
+
*
|
|
12825
|
+
* A plain call is the GET the first paint, the 60 s poll and the composer
|
|
12826
|
+
* badge use: it is cheap and never fans out one upstream request per account.
|
|
12827
|
+
* `refresh` is the forced path — the same POST /usage the card's own button
|
|
12828
|
+
* drives — and the host answers it with `{ usage, status }`; only the status
|
|
12829
|
+
* half is returned, because every caller renders a status card rather than the
|
|
12830
|
+
* raw quota payload.
|
|
12831
|
+
*/
|
|
12832
|
+
status(refresh = false) {
|
|
12833
|
+
if (!refresh) return api$7.get("/status");
|
|
12834
|
+
return api$7.post("/usage", {}).then((outcome) => outcome.status);
|
|
12812
12835
|
}
|
|
12813
12836
|
updateSettings(patch) {
|
|
12814
12837
|
return api$7.post("/settings", patch).then(() => this.status());
|
|
@@ -13186,6 +13209,14 @@ window.__ModuleLoader__.load({
|
|
|
13186
13209
|
//#endregion
|
|
13187
13210
|
//#region src/client/cline-pass/ClinePassSection.tsx
|
|
13188
13211
|
const API$3 = new ClinePassApi();
|
|
13212
|
+
/**
|
|
13213
|
+
* How often an open card re-reads the quota.
|
|
13214
|
+
*
|
|
13215
|
+
* The same 60 s every other line's card and composer badge uses: a window that
|
|
13216
|
+
* moves on a multi-hour scale does not need a faster poll, and a slower one
|
|
13217
|
+
* would leave a card visibly stale after the user spends a request.
|
|
13218
|
+
*/
|
|
13219
|
+
const QUOTA_POLL_MS = 6e4;
|
|
13189
13220
|
function detail$1(error) {
|
|
13190
13221
|
return error instanceof Error ? error.message : String(error);
|
|
13191
13222
|
}
|
|
@@ -13231,19 +13262,46 @@ window.__ModuleLoader__.load({
|
|
|
13231
13262
|
const [settingsForm, setSettingsForm] = (0, react.useState)(null);
|
|
13232
13263
|
const [history, setHistory] = (0, react.useState)(null);
|
|
13233
13264
|
const [loginFlow, setLoginFlow] = (0, react.useState)(null);
|
|
13234
|
-
|
|
13265
|
+
/** Follow-up poll owed while the host refreshes the quota behind an answer. */
|
|
13266
|
+
const quotaFollowUp = (0, react.useRef)(null);
|
|
13267
|
+
/**
|
|
13268
|
+
* One status read; `quiet` is every read nobody pressed a button for.
|
|
13269
|
+
*
|
|
13270
|
+
* A quiet read is silent on purpose: it never paints an error and never clears
|
|
13271
|
+
* one, so a poll that fails while a user reads the page, or types into a
|
|
13272
|
+
* settings field, does not change what is on screen. The manual actions below
|
|
13273
|
+
* still report their own failures loudly. The draft guards inside the two
|
|
13274
|
+
* setters are what keep a background answer from overwriting typed text.
|
|
13275
|
+
*/
|
|
13276
|
+
const loadStatus = (0, react.useCallback)(async (quiet = false) => {
|
|
13235
13277
|
try {
|
|
13236
13278
|
const next = await API$3.status();
|
|
13237
13279
|
setStatus(next);
|
|
13238
13280
|
setSettingsForm((current) => current ?? (next.settings === void 0 ? null : settingsFormOf$1(next)));
|
|
13239
|
-
setError(null);
|
|
13281
|
+
if (!quiet) setError(null);
|
|
13282
|
+
quotaFollowUp.current ??= createQuotaFollowUp();
|
|
13283
|
+
quotaFollowUp.current.observe(quotaRefreshingOf(next), () => {
|
|
13284
|
+
loadStatus(true);
|
|
13285
|
+
});
|
|
13240
13286
|
} catch (cause) {
|
|
13241
|
-
setError(detail$1(cause));
|
|
13287
|
+
if (!quiet) setError(detail$1(cause));
|
|
13242
13288
|
}
|
|
13243
13289
|
}, []);
|
|
13244
13290
|
(0, react.useEffect)(() => {
|
|
13245
|
-
|
|
13246
|
-
}, [
|
|
13291
|
+
loadStatus();
|
|
13292
|
+
}, [loadStatus]);
|
|
13293
|
+
(0, react.useEffect)(() => {
|
|
13294
|
+
const refreshWhenVisible = () => {
|
|
13295
|
+
if (document.visibilityState === "visible") loadStatus(true);
|
|
13296
|
+
};
|
|
13297
|
+
document.addEventListener("visibilitychange", refreshWhenVisible);
|
|
13298
|
+
const timer = window.setInterval(refreshWhenVisible, QUOTA_POLL_MS);
|
|
13299
|
+
return () => {
|
|
13300
|
+
window.clearInterval(timer);
|
|
13301
|
+
document.removeEventListener("visibilitychange", refreshWhenVisible);
|
|
13302
|
+
quotaFollowUp.current?.cancel();
|
|
13303
|
+
};
|
|
13304
|
+
}, [loadStatus]);
|
|
13247
13305
|
(0, react.useEffect)(() => {
|
|
13248
13306
|
if (status === null) return;
|
|
13249
13307
|
setDrafts((current) => {
|
|
@@ -13271,12 +13329,12 @@ window.__ModuleLoader__.load({
|
|
|
13271
13329
|
if (next.status === "complete") {
|
|
13272
13330
|
setNotice(zh$4.loginComplete.replace("{account}", next.accountAlias === "" ? "Cline Pass" : next.accountAlias));
|
|
13273
13331
|
setShowKeyForm(false);
|
|
13274
|
-
|
|
13332
|
+
loadStatus();
|
|
13275
13333
|
} else if (next.status === "failed") setError(zh$4.loginFailed.replace("{detail}", next.error));
|
|
13276
13334
|
}).catch((cause) => setError(detail$1(cause)));
|
|
13277
13335
|
}, Math.max(2, flow.intervalSeconds) * 1e3);
|
|
13278
13336
|
return () => window.clearInterval(timer);
|
|
13279
|
-
}, [loginFlow,
|
|
13337
|
+
}, [loginFlow, loadStatus]);
|
|
13280
13338
|
const startLogin = (0, react.useCallback)(() => {
|
|
13281
13339
|
setBusy("login");
|
|
13282
13340
|
setError(null);
|
|
@@ -21467,8 +21525,11 @@ window.__ModuleLoader__.load({
|
|
|
21467
21525
|
*
|
|
21468
21526
|
* Quota follows the ACCOUNT, and a pool has several. The badge therefore shows
|
|
21469
21527
|
* the most-consumed window across the accounts the host has already read, which
|
|
21470
|
-
* is the one that will stop the next request first
|
|
21471
|
-
* read
|
|
21528
|
+
* is the one that will stop the next request first. Its own 60 s poll is a plain
|
|
21529
|
+
* read, so a poll never fans out one upstream call per account; only an explicit
|
|
21530
|
+
* click on the badge asks the host for the forced refresh, the same POST the
|
|
21531
|
+
* card's own "refresh usage" button drives — which is exactly what the shared
|
|
21532
|
+
* badge's `refresh` argument is for, and what this line used to drop.
|
|
21472
21533
|
*/
|
|
21473
21534
|
function ClinePassComposerQuota(props) {
|
|
21474
21535
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerQuotaBadge, {
|
|
@@ -21476,7 +21537,7 @@ window.__ModuleLoader__.load({
|
|
|
21476
21537
|
loadModelDirectory: props.loadModelDirectory,
|
|
21477
21538
|
providerId: "cline-pass",
|
|
21478
21539
|
label: zh$4.composerLabel,
|
|
21479
|
-
readStatus: () => API$1.status(),
|
|
21540
|
+
readStatus: (refresh) => API$1.status(refresh),
|
|
21480
21541
|
selectFacts: selectClinePassFacts
|
|
21481
21542
|
});
|
|
21482
21543
|
}
|