@foxden-app/foxclaw 0.5.58 → 0.5.60
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/.env.example +3 -0
- package/CHANGELOG.md +20 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +1 -0
- package/dist/controller/controller.d.ts +4 -0
- package/dist/controller/controller.js +88 -33
- package/dist/store/database.d.ts +3 -1
- package/dist/store/database.js +14 -2
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -38,6 +38,9 @@ TELEGRAM_PREVIEW_THROTTLE_MS=800
|
|
|
38
38
|
# Set to false if you prefer to keep "read files / edited files / ran command"
|
|
39
39
|
# detail cards in the chat history.
|
|
40
40
|
# TELEGRAM_DELETE_TOOL_DETAILS_AFTER_FINAL=true
|
|
41
|
+
# Delete time-sensitive interactive panels such as /auth and /threads after 30 minutes.
|
|
42
|
+
# The timer restarts whenever the panel is refreshed. Set to 0 to disable.
|
|
43
|
+
# TELEGRAM_PANEL_TTL_MS=1800000
|
|
41
44
|
THREAD_LIST_LIMIT=10
|
|
42
45
|
CODEX_CLI_BIN=/absolute/path/to/codex
|
|
43
46
|
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable FoxClaw changes are listed here. Each release note is bilingual so GitHub Releases and the npm package are useful to both Chinese and English readers.
|
|
4
4
|
|
|
5
|
+
## 0.5.60 - 2026-06-21
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- 修复 `/auth` 刷新时间测试对 Asia/Shanghai 时区的硬编码,使 Linux、macOS 和 UTC CI runner 都按各自本地时区验证;功能内容与 0.5.59 一并发布。
|
|
9
|
+
|
|
10
|
+
### English
|
|
11
|
+
- Fixed the `/auth` reset-time test to respect the runner's local timezone across Linux, macOS, and UTC CI environments. This release publishes the feature set introduced in 0.5.59.
|
|
12
|
+
|
|
13
|
+
## 0.5.59 - 2026-06-21
|
|
14
|
+
|
|
15
|
+
### 中文
|
|
16
|
+
- `/auth` 富文本表格新增 Quota A、Quota B 的下次刷新时间;精确时间随额度快照持久化并可在节点间共享,旧快照安全显示 `-`。
|
|
17
|
+
- 修复 `/auth` 按钮回调后退化为普通消息的问题:切换、启停、筛选、翻页、修复、刷新和安全同步现在始终使用 RichMessage 编辑。
|
|
18
|
+
- 新增时效面板自动清理:`/auth`、`/threads` 和统一 `/setup` 面板默认空闲 30 分钟后删除,每次交互重新计时;可用 `TELEGRAM_PANEL_TTL_MS` 调整,设为 `0` 可关闭。
|
|
19
|
+
|
|
20
|
+
### English
|
|
21
|
+
- Added next-reset columns for Quota A and Quota B in the `/auth` RichMessage table. Exact reset timestamps now persist with quota snapshots and can be shared across nodes; legacy snapshots safely show `-`.
|
|
22
|
+
- Fixed `/auth` callback refreshes degrading to plain messages. Switch, toggle, filter, pagination, repair, refresh, and safe-sync actions now consistently edit the panel as a RichMessage.
|
|
23
|
+
- Added automatic cleanup for time-sensitive panels. `/auth`, `/threads`, and unified `/setup` panels are deleted after 30 minutes of inactivity by default, with interaction resetting the timer. Configure `TELEGRAM_PANEL_TTL_MS`, or set it to `0` to disable cleanup.
|
|
24
|
+
|
|
5
25
|
## 0.5.58 - 2026-06-21
|
|
6
26
|
|
|
7
27
|
### 中文
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -78,6 +78,7 @@ export function loadConfig() {
|
|
|
78
78
|
telegramPollIntervalMs: intEnv('TELEGRAM_POLL_INTERVAL_MS', 1200),
|
|
79
79
|
telegramPreviewThrottleMs: intEnv('TELEGRAM_PREVIEW_THROTTLE_MS', 800),
|
|
80
80
|
telegramDeleteToolDetailsAfterFinal: boolEnv('TELEGRAM_DELETE_TOOL_DETAILS_AFTER_FINAL', true),
|
|
81
|
+
telegramPanelTtlMs: intEnv('TELEGRAM_PANEL_TTL_MS', 30 * 60_000),
|
|
81
82
|
threadListLimit: intEnv('THREAD_LIST_LIMIT', 10),
|
|
82
83
|
statusPath: DEFAULT_STATUS_PATH,
|
|
83
84
|
logPath: DEFAULT_LOG_PATH,
|
|
@@ -96,6 +96,7 @@ export declare class BridgeSessionCore {
|
|
|
96
96
|
private proactiveAuthRefreshTimer;
|
|
97
97
|
private proactiveAuthRefreshInProgress;
|
|
98
98
|
private proactiveAuthRefreshStatus;
|
|
99
|
+
private stalePanelDeleteTimers;
|
|
99
100
|
private attachedThreads;
|
|
100
101
|
private botUsername;
|
|
101
102
|
private lastError;
|
|
@@ -236,6 +237,9 @@ export declare class BridgeSessionCore {
|
|
|
236
237
|
private editMessage;
|
|
237
238
|
private editHtmlMessage;
|
|
238
239
|
private editRichHtmlMessage;
|
|
240
|
+
private editRichInternalMessage;
|
|
241
|
+
private editAuthPanelMessage;
|
|
242
|
+
private scheduleStalePanelDeletion;
|
|
239
243
|
private deleteMessage;
|
|
240
244
|
private sendTyping;
|
|
241
245
|
private sendObservedCliUserMessage;
|
|
@@ -152,6 +152,7 @@ export class BridgeSessionCore {
|
|
|
152
152
|
proactiveAuthRefreshTimer = null;
|
|
153
153
|
proactiveAuthRefreshInProgress = false;
|
|
154
154
|
proactiveAuthRefreshStatus = null;
|
|
155
|
+
stalePanelDeleteTimers = new Map();
|
|
155
156
|
attachedThreads = new Set();
|
|
156
157
|
botUsername = null;
|
|
157
158
|
lastError = null;
|
|
@@ -276,6 +277,10 @@ export class BridgeSessionCore {
|
|
|
276
277
|
this.clearRestartPreviewRecoveryTimers();
|
|
277
278
|
this.clearSelfUpdateStatusPoll();
|
|
278
279
|
this.clearProactiveAuthRefreshTimer();
|
|
280
|
+
for (const timer of this.stalePanelDeleteTimers.values()) {
|
|
281
|
+
clearTimeout(timer);
|
|
282
|
+
}
|
|
283
|
+
this.stalePanelDeleteTimers.clear();
|
|
279
284
|
await this.app.stop({ terminateServer: false });
|
|
280
285
|
this.updateStatus();
|
|
281
286
|
}
|
|
@@ -3431,6 +3436,35 @@ export class BridgeSessionCore {
|
|
|
3431
3436
|
await this.editHtmlMessage(scopeId, messageId, fallbackHtml, inlineKeyboard);
|
|
3432
3437
|
}
|
|
3433
3438
|
}
|
|
3439
|
+
async editRichInternalMessage(scopeId, messageId, title, text, inlineKeyboard) {
|
|
3440
|
+
if (scopeId.startsWith(BRIDGE_SCOPE_WEIXIN_PREFIX)) {
|
|
3441
|
+
await this.editMessage(scopeId, messageId, text, inlineKeyboard);
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
await this.editRichHtmlMessage(scopeId, messageId, formatRichInternalMessage(title, text), escapeTelegramHtml(text), inlineKeyboard);
|
|
3445
|
+
}
|
|
3446
|
+
async editAuthPanelMessage(scopeId, messageId, text, inlineKeyboard) {
|
|
3447
|
+
await this.editRichInternalMessage(scopeId, messageId, '/auth', text, inlineKeyboard);
|
|
3448
|
+
this.scheduleStalePanelDeletion(scopeId, messageId);
|
|
3449
|
+
}
|
|
3450
|
+
scheduleStalePanelDeletion(scopeId, messageId) {
|
|
3451
|
+
if (this.config.telegramPanelTtlMs <= 0 || parseWeixinBridgeScope(scopeId)) {
|
|
3452
|
+
return;
|
|
3453
|
+
}
|
|
3454
|
+
const key = `${scopeId}:${messageId}`;
|
|
3455
|
+
const existing = this.stalePanelDeleteTimers.get(key);
|
|
3456
|
+
if (existing) {
|
|
3457
|
+
clearTimeout(existing);
|
|
3458
|
+
}
|
|
3459
|
+
const timer = setTimeout(() => {
|
|
3460
|
+
this.stalePanelDeleteTimers.delete(key);
|
|
3461
|
+
void this.deleteMessage(scopeId, messageId).catch(error => {
|
|
3462
|
+
this.logger.warn('telegram.stale_panel_delete_failed', { scopeId, messageId, error: toErrorMeta(error) });
|
|
3463
|
+
});
|
|
3464
|
+
}, this.config.telegramPanelTtlMs);
|
|
3465
|
+
timer.unref();
|
|
3466
|
+
this.stalePanelDeleteTimers.set(key, timer);
|
|
3467
|
+
}
|
|
3434
3468
|
async deleteMessage(scopeId, messageId) {
|
|
3435
3469
|
await this.messaging.deleteMessage(scopeId, messageId);
|
|
3436
3470
|
}
|
|
@@ -4859,6 +4893,7 @@ export class BridgeSessionCore {
|
|
|
4859
4893
|
const authListMessage = renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(scopeId) !== null, record);
|
|
4860
4894
|
const messageId = await this.sendRichInternalMessage(scopeId, '/auth', authListMessage, authChoiceKeyboard(locale, record));
|
|
4861
4895
|
record.messageId = messageId;
|
|
4896
|
+
this.scheduleStalePanelDeletion(scopeId, messageId);
|
|
4862
4897
|
}
|
|
4863
4898
|
async handleAuthSyncCommand(scopeId, locale, args) {
|
|
4864
4899
|
const action = args[0]?.toLowerCase() ?? 'status';
|
|
@@ -5664,12 +5699,12 @@ export class BridgeSessionCore {
|
|
|
5664
5699
|
}
|
|
5665
5700
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_sync_safe_starting'));
|
|
5666
5701
|
if (record.messageId !== null) {
|
|
5667
|
-
await this.
|
|
5702
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_safe_starting'), []);
|
|
5668
5703
|
}
|
|
5669
5704
|
const result = await this.runAuthSafeSyncAll();
|
|
5670
5705
|
if (!result) {
|
|
5671
5706
|
if (record.messageId !== null) {
|
|
5672
|
-
await this.
|
|
5707
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_disabled'), authChoiceKeyboard(locale, record));
|
|
5673
5708
|
}
|
|
5674
5709
|
return;
|
|
5675
5710
|
}
|
|
@@ -5679,7 +5714,7 @@ export class BridgeSessionCore {
|
|
|
5679
5714
|
record.createdAt = Date.now();
|
|
5680
5715
|
clampCodexAuthListOffset(record);
|
|
5681
5716
|
if (record.messageId !== null) {
|
|
5682
|
-
await this.
|
|
5717
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, `${t(locale, 'auth_sync_safe_done', {
|
|
5683
5718
|
localSynced: result.localSynced,
|
|
5684
5719
|
localSkipped: result.localSkipped,
|
|
5685
5720
|
sent: result.sent,
|
|
@@ -5695,7 +5730,7 @@ export class BridgeSessionCore {
|
|
|
5695
5730
|
}
|
|
5696
5731
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_refresh_all_confirm_short'));
|
|
5697
5732
|
if (record.messageId !== null) {
|
|
5698
|
-
await this.
|
|
5733
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_confirm_message'), authRefreshAllConfirmKeyboard(locale, record));
|
|
5699
5734
|
}
|
|
5700
5735
|
return;
|
|
5701
5736
|
}
|
|
@@ -5706,7 +5741,7 @@ export class BridgeSessionCore {
|
|
|
5706
5741
|
record.candidates = state.candidates;
|
|
5707
5742
|
record.createdAt = Date.now();
|
|
5708
5743
|
if (record.messageId !== null) {
|
|
5709
|
-
await this.
|
|
5744
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
|
|
5710
5745
|
}
|
|
5711
5746
|
return;
|
|
5712
5747
|
}
|
|
@@ -5717,12 +5752,12 @@ export class BridgeSessionCore {
|
|
|
5717
5752
|
}
|
|
5718
5753
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_refresh_all_starting'));
|
|
5719
5754
|
if (record.messageId !== null) {
|
|
5720
|
-
await this.
|
|
5755
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_starting'), []);
|
|
5721
5756
|
}
|
|
5722
5757
|
const lease = await this.coordinator?.acquireAuthRefreshLease?.('auth refresh all');
|
|
5723
5758
|
if (lease && !lease.ok) {
|
|
5724
5759
|
if (record.messageId !== null) {
|
|
5725
|
-
await this.
|
|
5760
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_lease_failed', { error: lease.reason ?? t(locale, 'unknown') }), authChoiceKeyboard(locale, record));
|
|
5726
5761
|
}
|
|
5727
5762
|
return;
|
|
5728
5763
|
}
|
|
@@ -5738,7 +5773,7 @@ export class BridgeSessionCore {
|
|
|
5738
5773
|
record.candidates = state.candidates;
|
|
5739
5774
|
record.createdAt = Date.now();
|
|
5740
5775
|
if (record.messageId !== null) {
|
|
5741
|
-
await this.
|
|
5776
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, `${formatAuthRefreshAllResult(locale, result)}\n\n${renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record)}`, authChoiceKeyboard(locale, record));
|
|
5742
5777
|
}
|
|
5743
5778
|
return;
|
|
5744
5779
|
}
|
|
@@ -5775,7 +5810,7 @@ export class BridgeSessionCore {
|
|
|
5775
5810
|
clampCodexAuthListOffset(record);
|
|
5776
5811
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'decision_recorded'));
|
|
5777
5812
|
if (record.messageId !== null) {
|
|
5778
|
-
await this.
|
|
5813
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
|
|
5779
5814
|
}
|
|
5780
5815
|
}
|
|
5781
5816
|
async handleAuthRepairMenuCallback(event, localId, index, locale) {
|
|
@@ -5795,7 +5830,7 @@ export class BridgeSessionCore {
|
|
|
5795
5830
|
}
|
|
5796
5831
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_repair_actions_short'));
|
|
5797
5832
|
if (record.messageId !== null) {
|
|
5798
|
-
await this.
|
|
5833
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_repair_actions_message', { value: formatCodexAuthCandidateDisplayName(candidate.name) }), authRepairKeyboard(locale, record, index));
|
|
5799
5834
|
}
|
|
5800
5835
|
}
|
|
5801
5836
|
async handleAuthRepairActionCallback(event, localId, action, index, locale) {
|
|
@@ -5820,7 +5855,7 @@ export class BridgeSessionCore {
|
|
|
5820
5855
|
record.createdAt = Date.now();
|
|
5821
5856
|
clampCodexAuthListOffset(record);
|
|
5822
5857
|
if (record.messageId !== null) {
|
|
5823
|
-
await this.
|
|
5858
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
|
|
5824
5859
|
}
|
|
5825
5860
|
return;
|
|
5826
5861
|
}
|
|
@@ -5831,7 +5866,7 @@ export class BridgeSessionCore {
|
|
|
5831
5866
|
if (action === 'login') {
|
|
5832
5867
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'login_device_started'));
|
|
5833
5868
|
if (record.messageId !== null) {
|
|
5834
|
-
await this.
|
|
5869
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_repair_login_preparing', { value: candidate.name }), []);
|
|
5835
5870
|
}
|
|
5836
5871
|
await this.startAuthRepairLogin(event.scopeId, locale, candidate);
|
|
5837
5872
|
return;
|
|
@@ -5843,7 +5878,7 @@ export class BridgeSessionCore {
|
|
|
5843
5878
|
record.createdAt = Date.now();
|
|
5844
5879
|
clampCodexAuthListOffset(record);
|
|
5845
5880
|
if (record.messageId !== null) {
|
|
5846
|
-
await this.
|
|
5881
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, `${t(locale, 'auth_candidate_deleted', { value: candidate.name })}${restarted ? `\n${t(locale, 'auth_delete_current_restarted')}` : ''}\n\n${renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record)}`, authChoiceKeyboard(locale, record));
|
|
5847
5882
|
}
|
|
5848
5883
|
}
|
|
5849
5884
|
async handleAuthToggleCallback(event, localId, index, locale) {
|
|
@@ -5868,7 +5903,7 @@ export class BridgeSessionCore {
|
|
|
5868
5903
|
clampCodexAuthListOffset(record);
|
|
5869
5904
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, disabled ? 'auth_candidate_disabled_short' : 'auth_candidate_enabled_short'));
|
|
5870
5905
|
if (record.messageId !== null) {
|
|
5871
|
-
await this.
|
|
5906
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
|
|
5872
5907
|
}
|
|
5873
5908
|
}
|
|
5874
5909
|
async handleAuthSwitchCallback(event, localId, index, locale) {
|
|
@@ -5898,7 +5933,7 @@ export class BridgeSessionCore {
|
|
|
5898
5933
|
const switchLabels = await this.readCodexAuthSwitchLabels(candidate);
|
|
5899
5934
|
const switchingMessage = t(locale, 'auth_switching', this.codexAuthSwitchParams(locale, switchLabels.fromLabel, switchLabels.toLabel));
|
|
5900
5935
|
if (record.messageId !== null) {
|
|
5901
|
-
await this.
|
|
5936
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, switchingMessage, []);
|
|
5902
5937
|
}
|
|
5903
5938
|
const outcome = await this.switchCodexAuthAndRestart(event.scopeId, locale, candidate, false, false);
|
|
5904
5939
|
const state = await this.listCodexAuthState();
|
|
@@ -5906,7 +5941,7 @@ export class BridgeSessionCore {
|
|
|
5906
5941
|
record.createdAt = Date.now();
|
|
5907
5942
|
clampCodexAuthListOffset(record);
|
|
5908
5943
|
if (record.messageId !== null) {
|
|
5909
|
-
await this.
|
|
5944
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, [
|
|
5910
5945
|
...this.formatAuthSwitchValidationLines(locale, outcome),
|
|
5911
5946
|
renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record),
|
|
5912
5947
|
].filter(Boolean).join('\n\n'), authChoiceKeyboard(locale, record));
|
|
@@ -6839,6 +6874,7 @@ export class BridgeSessionCore {
|
|
|
6839
6874
|
? buildThreadsKeyboard(locale, threadLikes, binding.threadId)
|
|
6840
6875
|
: buildThreadListKeyboard(locale, threadLikes, listState, binding.threadId);
|
|
6841
6876
|
await this.editHtmlMessage(scopeId, event.messageId, text, keyboard);
|
|
6877
|
+
this.scheduleStalePanelDeletion(scopeId, event.messageId);
|
|
6842
6878
|
}
|
|
6843
6879
|
let callbackText = t(locale, 'thread_opened');
|
|
6844
6880
|
if (this.config.codexAppSyncOnOpen) {
|
|
@@ -7289,9 +7325,11 @@ export class BridgeSessionCore {
|
|
|
7289
7325
|
: buildThreadListKeyboard(locale, forDisplay, presentationState, binding?.threadId ?? null);
|
|
7290
7326
|
if (messageId !== undefined) {
|
|
7291
7327
|
await this.editHtmlMessage(scopeId, messageId, text, keyboard);
|
|
7328
|
+
this.scheduleStalePanelDeletion(scopeId, messageId);
|
|
7292
7329
|
return;
|
|
7293
7330
|
}
|
|
7294
|
-
await this.sendHtmlMessage(scopeId, text, keyboard);
|
|
7331
|
+
const sentMessageId = await this.sendHtmlMessage(scopeId, text, keyboard);
|
|
7332
|
+
this.scheduleStalePanelDeletion(scopeId, sentMessageId);
|
|
7295
7333
|
}
|
|
7296
7334
|
async showModelSettingsPanel(scopeId, messageId, locale = this.localeForChat(scopeId)) {
|
|
7297
7335
|
const models = await this.app.listModels();
|
|
@@ -7321,9 +7359,11 @@ export class BridgeSessionCore {
|
|
|
7321
7359
|
const keyboard = buildSetupPanelKeyboard(locale, { focus, models, settings, access });
|
|
7322
7360
|
if (messageId !== undefined) {
|
|
7323
7361
|
await this.editHtmlMessage(scopeId, messageId, text, keyboard);
|
|
7362
|
+
this.scheduleStalePanelDeletion(scopeId, messageId);
|
|
7324
7363
|
return;
|
|
7325
7364
|
}
|
|
7326
|
-
await this.sendHtmlMessage(scopeId, text, keyboard);
|
|
7365
|
+
const sentMessageId = await this.sendHtmlMessage(scopeId, text, keyboard);
|
|
7366
|
+
this.scheduleStalePanelDeletion(scopeId, sentMessageId);
|
|
7327
7367
|
}
|
|
7328
7368
|
async showAccessSettingsPanel(scopeId, messageId, locale = this.localeForChat(scopeId)) {
|
|
7329
7369
|
const access = this.resolveEffectiveAccess(scopeId);
|
|
@@ -9004,12 +9044,14 @@ function parseRichAuthCandidateRow(line) {
|
|
|
9004
9044
|
if (!name) {
|
|
9005
9045
|
return null;
|
|
9006
9046
|
}
|
|
9007
|
-
const quotas = parts.map(
|
|
9047
|
+
const quotas = parts.map(parseRichAuthQuotaCell);
|
|
9008
9048
|
const statusParts = splitRichAuthStatus(status);
|
|
9009
9049
|
return {
|
|
9010
9050
|
index: match[1],
|
|
9011
|
-
quotaA: quotas[0] ?? '-',
|
|
9012
|
-
|
|
9051
|
+
quotaA: quotas[0]?.value ?? '-',
|
|
9052
|
+
quotaAReset: quotas[0]?.reset ?? '-',
|
|
9053
|
+
quotaB: quotas[1]?.value ?? '-',
|
|
9054
|
+
quotaBReset: quotas[1]?.reset ?? '-',
|
|
9013
9055
|
name,
|
|
9014
9056
|
current,
|
|
9015
9057
|
enabled: statusParts.health === 'disabled' || statusParts.health === '已禁用'
|
|
@@ -9021,12 +9063,14 @@ function parseRichAuthCandidateRow(line) {
|
|
|
9021
9063
|
function formatRichAuthCandidateTable(rows) {
|
|
9022
9064
|
return [
|
|
9023
9065
|
'<table bordered striped>',
|
|
9024
|
-
'<tr><th>#</th><th>Quota A</th><th>Quota B</th><th>Auth</th><th>Current</th><th>Plan</th><th>Health</th><th>Last refresh</th><th>Expiry</th><th>Risk</th><th>Command</th></tr>',
|
|
9066
|
+
'<tr><th>#</th><th>Quota A</th><th>A reset</th><th>Quota B</th><th>B reset</th><th>Auth</th><th>Current</th><th>Plan</th><th>Health</th><th>Last refresh</th><th>Expiry</th><th>Risk</th><th>Command</th></tr>',
|
|
9025
9067
|
...rows.map(row => [
|
|
9026
9068
|
'<tr>',
|
|
9027
9069
|
`<td>${escapeTelegramHtml(row.index)}</td>`,
|
|
9028
9070
|
`<td>${escapeTelegramHtml(row.quotaA)}</td>`,
|
|
9071
|
+
`<td>${escapeTelegramHtml(row.quotaAReset)}</td>`,
|
|
9029
9072
|
`<td>${escapeTelegramHtml(row.quotaB)}</td>`,
|
|
9073
|
+
`<td>${escapeTelegramHtml(row.quotaBReset)}</td>`,
|
|
9030
9074
|
`<td>${formatRichAuthCommandLink(`/auth use ${row.index}`, row.name)}</td>`,
|
|
9031
9075
|
`<td>${row.current ? 'yes' : '-'}</td>`,
|
|
9032
9076
|
`<td>${escapeTelegramHtml(row.plan)}</td>`,
|
|
@@ -9077,16 +9121,17 @@ function formatRichAuthRisk(health) {
|
|
|
9077
9121
|
}
|
|
9078
9122
|
return '-';
|
|
9079
9123
|
}
|
|
9080
|
-
function
|
|
9124
|
+
function parseRichAuthQuotaCell(value) {
|
|
9081
9125
|
const trimmed = value.trim();
|
|
9082
9126
|
if (!trimmed || trimmed === '--' || trimmed === '—') {
|
|
9083
|
-
return '-';
|
|
9127
|
+
return { value: '-', reset: '-' };
|
|
9084
9128
|
}
|
|
9085
|
-
const [
|
|
9129
|
+
const [quotaPart, reset = '-'] = trimmed.split('@', 2);
|
|
9130
|
+
const [windowLabel, percent] = quotaPart.split(':');
|
|
9086
9131
|
if (!windowLabel || percent === undefined) {
|
|
9087
|
-
return
|
|
9132
|
+
return { value: quotaPart, reset };
|
|
9088
9133
|
}
|
|
9089
|
-
return `${percent}
|
|
9134
|
+
return { value: `${percent}%`, reset };
|
|
9090
9135
|
}
|
|
9091
9136
|
function formatRichAuthCommandCell(row) {
|
|
9092
9137
|
const commands = [formatRichAuthCommandLink(`/auth use ${row.index}`, 'use')];
|
|
@@ -11135,8 +11180,10 @@ function authQuotaSnapshotFromRateLimit(snapshot, accountId = null, quotaIdentit
|
|
|
11135
11180
|
planType: snapshot.planType,
|
|
11136
11181
|
primaryWindowDurationMins: snapshot.primary?.windowDurationMins ?? null,
|
|
11137
11182
|
primaryRemainingPercent: snapshot.primary ? remainingUsagePercent(snapshot.primary.usedPercent) : null,
|
|
11183
|
+
primaryResetsAt: snapshot.primary?.resetsAt ?? null,
|
|
11138
11184
|
secondaryWindowDurationMins: snapshot.secondary?.windowDurationMins ?? null,
|
|
11139
11185
|
secondaryRemainingPercent: snapshot.secondary ? remainingUsagePercent(snapshot.secondary.usedPercent) : null,
|
|
11186
|
+
secondaryResetsAt: snapshot.secondary?.resetsAt ?? null,
|
|
11140
11187
|
};
|
|
11141
11188
|
}
|
|
11142
11189
|
function codexAuthQuotaSnapshotFromRecord(record) {
|
|
@@ -11147,8 +11194,10 @@ function codexAuthQuotaSnapshotFromRecord(record) {
|
|
|
11147
11194
|
planType: record.planType,
|
|
11148
11195
|
primaryWindowDurationMins: record.primaryWindowDurationMins,
|
|
11149
11196
|
primaryRemainingPercent: record.primaryRemainingPercent,
|
|
11197
|
+
primaryResetsAt: record.primaryResetsAt,
|
|
11150
11198
|
secondaryWindowDurationMins: record.secondaryWindowDurationMins,
|
|
11151
11199
|
secondaryRemainingPercent: record.secondaryRemainingPercent,
|
|
11200
|
+
secondaryResetsAt: record.secondaryResetsAt,
|
|
11152
11201
|
};
|
|
11153
11202
|
}
|
|
11154
11203
|
function mergeCodexAuthQuotaSnapshots(current, incoming) {
|
|
@@ -11171,8 +11220,10 @@ function isFiniteCodexAuthQuotaSnapshotRecord(record) {
|
|
|
11171
11220
|
&& isNullableString(record.planType)
|
|
11172
11221
|
&& isNullableFiniteNumber(record.primaryWindowDurationMins)
|
|
11173
11222
|
&& isNullableFiniteNumber(record.primaryRemainingPercent)
|
|
11223
|
+
&& isNullableFiniteNumber(record.primaryResetsAt)
|
|
11174
11224
|
&& isNullableFiniteNumber(record.secondaryWindowDurationMins)
|
|
11175
|
-
&& isNullableFiniteNumber(record.secondaryRemainingPercent)
|
|
11225
|
+
&& isNullableFiniteNumber(record.secondaryRemainingPercent)
|
|
11226
|
+
&& isNullableFiniteNumber(record.secondaryResetsAt);
|
|
11176
11227
|
}
|
|
11177
11228
|
function remainingUsagePercent(usedPercent) {
|
|
11178
11229
|
if (!Number.isFinite(usedPercent)) {
|
|
@@ -11185,12 +11236,12 @@ function formatAuthQuotaPrefix(locale, snapshot) {
|
|
|
11185
11236
|
return '--';
|
|
11186
11237
|
}
|
|
11187
11238
|
const windows = [
|
|
11188
|
-
[snapshot.primaryWindowDurationMins, snapshot.primaryRemainingPercent, 'primary'],
|
|
11189
|
-
[snapshot.secondaryWindowDurationMins, snapshot.secondaryRemainingPercent, 'secondary'],
|
|
11239
|
+
[snapshot.primaryWindowDurationMins, snapshot.primaryRemainingPercent, snapshot.primaryResetsAt, 'primary'],
|
|
11240
|
+
[snapshot.secondaryWindowDurationMins, snapshot.secondaryRemainingPercent, snapshot.secondaryResetsAt, 'secondary'],
|
|
11190
11241
|
];
|
|
11191
11242
|
const values = windows
|
|
11192
|
-
.filter(([duration, remaining]) => duration !== null || remaining !== null)
|
|
11193
|
-
.map(([duration, remaining, fallback]) => (`${formatCompactRateLimitWindowLabel(locale, duration, fallback)}:${remaining === null ? '--' : formatUsagePercent(remaining)}`));
|
|
11243
|
+
.filter(([duration, remaining, resetsAt]) => duration !== null || remaining !== null || resetsAt !== null)
|
|
11244
|
+
.map(([duration, remaining, resetsAt, fallback]) => (`${formatCompactRateLimitWindowLabel(locale, duration, fallback)}:${remaining === null ? '--' : formatUsagePercent(remaining)}${resetsAt === null ? '' : `@${formatLocalTimestamp(resetsAt)}`}`));
|
|
11194
11245
|
return values.length > 0 ? values.join('|') : '--';
|
|
11195
11246
|
}
|
|
11196
11247
|
function formatAuthQuotaButtonPrefix(snapshot) {
|
|
@@ -11216,8 +11267,10 @@ function isCodexAuthQuotaSnapshot(value) {
|
|
|
11216
11267
|
&& (snapshot.planType === undefined || isNullableString(snapshot.planType))
|
|
11217
11268
|
&& (snapshot.primaryWindowDurationMins === undefined || isNullableFiniteNumber(snapshot.primaryWindowDurationMins))
|
|
11218
11269
|
&& isNullableFiniteNumber(snapshot.primaryRemainingPercent)
|
|
11270
|
+
&& (snapshot.primaryResetsAt === undefined || isNullableFiniteNumber(snapshot.primaryResetsAt))
|
|
11219
11271
|
&& (snapshot.secondaryWindowDurationMins === undefined || isNullableFiniteNumber(snapshot.secondaryWindowDurationMins))
|
|
11220
|
-
&& isNullableFiniteNumber(snapshot.secondaryRemainingPercent)
|
|
11272
|
+
&& isNullableFiniteNumber(snapshot.secondaryRemainingPercent)
|
|
11273
|
+
&& (snapshot.secondaryResetsAt === undefined || isNullableFiniteNumber(snapshot.secondaryResetsAt));
|
|
11221
11274
|
}
|
|
11222
11275
|
function normalizeCodexAuthQuotaSnapshot(snapshot) {
|
|
11223
11276
|
return {
|
|
@@ -11227,8 +11280,10 @@ function normalizeCodexAuthQuotaSnapshot(snapshot) {
|
|
|
11227
11280
|
planType: snapshot.planType ?? null,
|
|
11228
11281
|
primaryWindowDurationMins: snapshot.primaryWindowDurationMins ?? null,
|
|
11229
11282
|
primaryRemainingPercent: snapshot.primaryRemainingPercent,
|
|
11283
|
+
primaryResetsAt: snapshot.primaryResetsAt ?? null,
|
|
11230
11284
|
secondaryWindowDurationMins: snapshot.secondaryWindowDurationMins ?? null,
|
|
11231
11285
|
secondaryRemainingPercent: snapshot.secondaryRemainingPercent,
|
|
11286
|
+
secondaryResetsAt: snapshot.secondaryResetsAt ?? null,
|
|
11232
11287
|
};
|
|
11233
11288
|
}
|
|
11234
11289
|
function chatGptAuthMetadataCompatible(left, right) {
|
package/dist/store/database.d.ts
CHANGED
|
@@ -35,8 +35,10 @@ export interface CodexAuthQuotaSnapshotRecord {
|
|
|
35
35
|
planType: string | null;
|
|
36
36
|
primaryWindowDurationMins: number | null;
|
|
37
37
|
primaryRemainingPercent: number | null;
|
|
38
|
+
primaryResetsAt: number | null;
|
|
38
39
|
secondaryWindowDurationMins: number | null;
|
|
39
40
|
secondaryRemainingPercent: number | null;
|
|
41
|
+
secondaryResetsAt: number | null;
|
|
40
42
|
updatedAt: number;
|
|
41
43
|
}
|
|
42
44
|
export interface CodexAuthPoolStats {
|
|
@@ -136,7 +138,7 @@ export declare class BridgeStore {
|
|
|
136
138
|
recordCodexAuthCandidateInvalidDelete(name: string, reason?: string | null): void;
|
|
137
139
|
recordCodexAuthCandidateRemoved(name: string, reason?: string | null): void;
|
|
138
140
|
getCodexAuthPoolStats(): CodexAuthPoolStats;
|
|
139
|
-
setCodexAuthQuotaSnapshot(runtimeId: string, candidateName: string, accountId: string, quotaIdentityId: string, snapshot: Pick<CodexAuthQuotaSnapshotRecord, 'capturedAtMs' | 'planType' | 'primaryWindowDurationMins' | 'primaryRemainingPercent' | 'secondaryWindowDurationMins' | 'secondaryRemainingPercent'>): void;
|
|
141
|
+
setCodexAuthQuotaSnapshot(runtimeId: string, candidateName: string, accountId: string, quotaIdentityId: string, snapshot: Pick<CodexAuthQuotaSnapshotRecord, 'capturedAtMs' | 'planType' | 'primaryWindowDurationMins' | 'primaryRemainingPercent' | 'secondaryWindowDurationMins' | 'secondaryRemainingPercent'> & Partial<Pick<CodexAuthQuotaSnapshotRecord, 'primaryResetsAt' | 'secondaryResetsAt'>>): void;
|
|
140
142
|
listCodexAuthQuotaSnapshots(quotaIdentityIds: string[]): CodexAuthQuotaSnapshotRecord[];
|
|
141
143
|
private ensureColumn;
|
|
142
144
|
}
|
package/dist/store/database.js
CHANGED
|
@@ -191,8 +191,10 @@ export class BridgeStore {
|
|
|
191
191
|
plan_type TEXT,
|
|
192
192
|
primary_window_duration_mins REAL,
|
|
193
193
|
primary_remaining_percent REAL,
|
|
194
|
+
primary_resets_at INTEGER,
|
|
194
195
|
secondary_window_duration_mins REAL,
|
|
195
196
|
secondary_remaining_percent REAL,
|
|
197
|
+
secondary_resets_at INTEGER,
|
|
196
198
|
updated_at INTEGER NOT NULL,
|
|
197
199
|
PRIMARY KEY (runtime_id, candidate_name)
|
|
198
200
|
);
|
|
@@ -225,7 +227,9 @@ export class BridgeStore {
|
|
|
225
227
|
this.ensureColumn('codex_auth_candidate_runtime', 'state', "TEXT NOT NULL DEFAULT 'active'");
|
|
226
228
|
this.ensureColumn('codex_auth_quota_snapshots', 'plan_type', 'TEXT');
|
|
227
229
|
this.ensureColumn('codex_auth_quota_snapshots', 'primary_window_duration_mins', 'REAL');
|
|
230
|
+
this.ensureColumn('codex_auth_quota_snapshots', 'primary_resets_at', 'INTEGER');
|
|
228
231
|
this.ensureColumn('codex_auth_quota_snapshots', 'secondary_window_duration_mins', 'REAL');
|
|
232
|
+
this.ensureColumn('codex_auth_quota_snapshots', 'secondary_resets_at', 'INTEGER');
|
|
229
233
|
this.ensureColumn('codex_auth_quota_snapshots', 'quota_identity_id', "TEXT NOT NULL DEFAULT ''");
|
|
230
234
|
this.ensureColumn('codex_auth_pool_history', 'invalid_delete_count', 'INTEGER NOT NULL DEFAULT 0');
|
|
231
235
|
this.db.prepare(`
|
|
@@ -997,11 +1001,13 @@ export class BridgeStore {
|
|
|
997
1001
|
plan_type,
|
|
998
1002
|
primary_window_duration_mins,
|
|
999
1003
|
primary_remaining_percent,
|
|
1004
|
+
primary_resets_at,
|
|
1000
1005
|
secondary_window_duration_mins,
|
|
1001
1006
|
secondary_remaining_percent,
|
|
1007
|
+
secondary_resets_at,
|
|
1002
1008
|
updated_at
|
|
1003
1009
|
)
|
|
1004
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1010
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1005
1011
|
ON CONFLICT(runtime_id, candidate_name) DO UPDATE SET
|
|
1006
1012
|
account_id = excluded.account_id,
|
|
1007
1013
|
quota_identity_id = excluded.quota_identity_id,
|
|
@@ -1009,10 +1015,12 @@ export class BridgeStore {
|
|
|
1009
1015
|
plan_type = excluded.plan_type,
|
|
1010
1016
|
primary_window_duration_mins = excluded.primary_window_duration_mins,
|
|
1011
1017
|
primary_remaining_percent = excluded.primary_remaining_percent,
|
|
1018
|
+
primary_resets_at = excluded.primary_resets_at,
|
|
1012
1019
|
secondary_window_duration_mins = excluded.secondary_window_duration_mins,
|
|
1013
1020
|
secondary_remaining_percent = excluded.secondary_remaining_percent,
|
|
1021
|
+
secondary_resets_at = excluded.secondary_resets_at,
|
|
1014
1022
|
updated_at = excluded.updated_at
|
|
1015
|
-
`).run(runtimeId, candidateName, accountId, quotaIdentityId, snapshot.capturedAtMs, snapshot.planType, snapshot.primaryWindowDurationMins, snapshot.primaryRemainingPercent, snapshot.secondaryWindowDurationMins, snapshot.secondaryRemainingPercent, Date.now());
|
|
1023
|
+
`).run(runtimeId, candidateName, accountId, quotaIdentityId, snapshot.capturedAtMs, snapshot.planType, snapshot.primaryWindowDurationMins, snapshot.primaryRemainingPercent, snapshot.primaryResetsAt ?? null, snapshot.secondaryWindowDurationMins, snapshot.secondaryRemainingPercent, snapshot.secondaryResetsAt ?? null, Date.now());
|
|
1016
1024
|
}
|
|
1017
1025
|
listCodexAuthQuotaSnapshots(quotaIdentityIds) {
|
|
1018
1026
|
const uniqueQuotaIdentityIds = [...new Set(quotaIdentityIds.filter(Boolean))];
|
|
@@ -1030,8 +1038,10 @@ export class BridgeStore {
|
|
|
1030
1038
|
plan_type,
|
|
1031
1039
|
primary_window_duration_mins,
|
|
1032
1040
|
primary_remaining_percent,
|
|
1041
|
+
primary_resets_at,
|
|
1033
1042
|
secondary_window_duration_mins,
|
|
1034
1043
|
secondary_remaining_percent,
|
|
1044
|
+
secondary_resets_at,
|
|
1035
1045
|
updated_at
|
|
1036
1046
|
FROM codex_auth_quota_snapshots
|
|
1037
1047
|
WHERE quota_identity_id IN (${placeholders})
|
|
@@ -1045,8 +1055,10 @@ export class BridgeStore {
|
|
|
1045
1055
|
planType: nullableString(row.plan_type),
|
|
1046
1056
|
primaryWindowDurationMins: nullableNumber(row.primary_window_duration_mins),
|
|
1047
1057
|
primaryRemainingPercent: nullableNumber(row.primary_remaining_percent),
|
|
1058
|
+
primaryResetsAt: nullableNumber(row.primary_resets_at),
|
|
1048
1059
|
secondaryWindowDurationMins: nullableNumber(row.secondary_window_duration_mins),
|
|
1049
1060
|
secondaryRemainingPercent: nullableNumber(row.secondary_remaining_percent),
|
|
1061
|
+
secondaryResetsAt: nullableNumber(row.secondary_resets_at),
|
|
1050
1062
|
updatedAt: Number(row.updated_at),
|
|
1051
1063
|
}));
|
|
1052
1064
|
}
|