@foxden-app/foxclaw 0.5.57 → 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 +4 -1
- package/CHANGELOG.md +30 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/controller/controller.d.ts +4 -0
- package/dist/controller/controller.js +95 -35
- package/dist/store/database.d.ts +5 -1
- package/dist/store/database.js +38 -6
- 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
|
|
|
@@ -81,7 +84,7 @@ CODEX_CLI_BIN=/absolute/path/to/codex
|
|
|
81
84
|
# VOICE_SUMMARY_BUTTON_ENABLED=true
|
|
82
85
|
# VOICE_SUMMARY_TEXT_LIMIT=180
|
|
83
86
|
# VOICE_TEXT_LIMIT=2800
|
|
84
|
-
# VOICE_TTS_TIMEOUT_MS=
|
|
87
|
+
# VOICE_TTS_TIMEOUT_MS=300000
|
|
85
88
|
|
|
86
89
|
# Optional: standard HTTP(S) proxy for Telegram and ChatGPT/Codex backend requests.
|
|
87
90
|
# Put these in the same env file that `foxclaw start` installs into systemd/launchd.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
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
|
+
|
|
25
|
+
## 0.5.58 - 2026-06-21
|
|
26
|
+
|
|
27
|
+
### 中文
|
|
28
|
+
- 修复重启恢复中的操作明细清理:FoxClaw 现在会把已折叠工具明细的 Telegram message id 持久化到 active turn preview,重启后最终答复完成时仍能按配置删除这些明细消息。
|
|
29
|
+
- 将 `VOICE_TTS_TIMEOUT_MS` 默认值和示例配置改为 300000ms,适配 SoulX 等较慢语音后端;本机配置也已同步为 5 分钟。
|
|
30
|
+
|
|
31
|
+
### English
|
|
32
|
+
- Fixed operation-detail cleanup after restart recovery. FoxClaw now persists archived tool-detail Telegram message ids with active turn previews, so final-answer cleanup can still delete them after a service restart.
|
|
33
|
+
- Changed the default and example `VOICE_TTS_TIMEOUT_MS` to 300000ms for slower voice backends such as SoulX; the local deployment config has been updated to five minutes as well.
|
|
34
|
+
|
|
5
35
|
## 0.5.57 - 2026-06-21
|
|
6
36
|
|
|
7
37
|
### 中文
|
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,
|
|
@@ -110,7 +111,7 @@ export function loadConfig() {
|
|
|
110
111
|
voiceSummaryButtonEnabled: boolEnv('VOICE_SUMMARY_BUTTON_ENABLED', true),
|
|
111
112
|
voiceSummaryTextLimit: intEnv('VOICE_SUMMARY_TEXT_LIMIT', 180),
|
|
112
113
|
voiceTextLimit: intEnv('VOICE_TEXT_LIMIT', 2800),
|
|
113
|
-
voiceTtsTimeoutMs: intEnv('VOICE_TTS_TIMEOUT_MS',
|
|
114
|
+
voiceTtsTimeoutMs: intEnv('VOICE_TTS_TIMEOUT_MS', 300_000),
|
|
114
115
|
};
|
|
115
116
|
ensureAppDirs(config);
|
|
116
117
|
return config;
|
|
@@ -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
|
}
|
|
@@ -2909,8 +2914,9 @@ export class BridgeSessionCore {
|
|
|
2909
2914
|
const messageId = await this.sendMessage(record.scopeId, text, keyboard);
|
|
2910
2915
|
this.store.updatePendingAttachmentBatchMessage(record.batchId, messageId);
|
|
2911
2916
|
}
|
|
2912
|
-
async registerActiveTurn(scopeId, chatId, chatType, topicId, threadId, turnId, previewMessageId, authRetry = null, collaborationMode = DEFAULT_COLLABORATION_MODE, queuedInputId = null) {
|
|
2917
|
+
async registerActiveTurn(scopeId, chatId, chatType, topicId, threadId, turnId, previewMessageId, authRetry = null, collaborationMode = DEFAULT_COLLABORATION_MODE, queuedInputId = null, archivedMessageIds = []) {
|
|
2913
2918
|
const active = this.createActiveTurnState(scopeId, chatId, chatType, topicId, threadId, turnId, previewMessageId, false, collaborationMode, queuedInputId);
|
|
2919
|
+
active.archivedMessageIds = [...archivedMessageIds];
|
|
2914
2920
|
active.authRetry = authRetry;
|
|
2915
2921
|
this.setActiveTurn(scopeId, turnId, active);
|
|
2916
2922
|
const pendingError = this.pendingTurnErrors.get(turnId);
|
|
@@ -2925,6 +2931,7 @@ export class BridgeSessionCore {
|
|
|
2925
2931
|
threadId,
|
|
2926
2932
|
messageId: previewMessageId,
|
|
2927
2933
|
isObserved: active.isObserved,
|
|
2934
|
+
archivedMessageIds: active.archivedMessageIds,
|
|
2928
2935
|
});
|
|
2929
2936
|
}
|
|
2930
2937
|
this.updateStatus();
|
|
@@ -3429,6 +3436,35 @@ export class BridgeSessionCore {
|
|
|
3429
3436
|
await this.editHtmlMessage(scopeId, messageId, fallbackHtml, inlineKeyboard);
|
|
3430
3437
|
}
|
|
3431
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
|
+
}
|
|
3432
3468
|
async deleteMessage(scopeId, messageId) {
|
|
3433
3469
|
await this.messaging.deleteMessage(scopeId, messageId);
|
|
3434
3470
|
}
|
|
@@ -4857,6 +4893,7 @@ export class BridgeSessionCore {
|
|
|
4857
4893
|
const authListMessage = renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(scopeId) !== null, record);
|
|
4858
4894
|
const messageId = await this.sendRichInternalMessage(scopeId, '/auth', authListMessage, authChoiceKeyboard(locale, record));
|
|
4859
4895
|
record.messageId = messageId;
|
|
4896
|
+
this.scheduleStalePanelDeletion(scopeId, messageId);
|
|
4860
4897
|
}
|
|
4861
4898
|
async handleAuthSyncCommand(scopeId, locale, args) {
|
|
4862
4899
|
const action = args[0]?.toLowerCase() ?? 'status';
|
|
@@ -5662,12 +5699,12 @@ export class BridgeSessionCore {
|
|
|
5662
5699
|
}
|
|
5663
5700
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_sync_safe_starting'));
|
|
5664
5701
|
if (record.messageId !== null) {
|
|
5665
|
-
await this.
|
|
5702
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_safe_starting'), []);
|
|
5666
5703
|
}
|
|
5667
5704
|
const result = await this.runAuthSafeSyncAll();
|
|
5668
5705
|
if (!result) {
|
|
5669
5706
|
if (record.messageId !== null) {
|
|
5670
|
-
await this.
|
|
5707
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_disabled'), authChoiceKeyboard(locale, record));
|
|
5671
5708
|
}
|
|
5672
5709
|
return;
|
|
5673
5710
|
}
|
|
@@ -5677,7 +5714,7 @@ export class BridgeSessionCore {
|
|
|
5677
5714
|
record.createdAt = Date.now();
|
|
5678
5715
|
clampCodexAuthListOffset(record);
|
|
5679
5716
|
if (record.messageId !== null) {
|
|
5680
|
-
await this.
|
|
5717
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, `${t(locale, 'auth_sync_safe_done', {
|
|
5681
5718
|
localSynced: result.localSynced,
|
|
5682
5719
|
localSkipped: result.localSkipped,
|
|
5683
5720
|
sent: result.sent,
|
|
@@ -5693,7 +5730,7 @@ export class BridgeSessionCore {
|
|
|
5693
5730
|
}
|
|
5694
5731
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_refresh_all_confirm_short'));
|
|
5695
5732
|
if (record.messageId !== null) {
|
|
5696
|
-
await this.
|
|
5733
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_confirm_message'), authRefreshAllConfirmKeyboard(locale, record));
|
|
5697
5734
|
}
|
|
5698
5735
|
return;
|
|
5699
5736
|
}
|
|
@@ -5704,7 +5741,7 @@ export class BridgeSessionCore {
|
|
|
5704
5741
|
record.candidates = state.candidates;
|
|
5705
5742
|
record.createdAt = Date.now();
|
|
5706
5743
|
if (record.messageId !== null) {
|
|
5707
|
-
await this.
|
|
5744
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
|
|
5708
5745
|
}
|
|
5709
5746
|
return;
|
|
5710
5747
|
}
|
|
@@ -5715,12 +5752,12 @@ export class BridgeSessionCore {
|
|
|
5715
5752
|
}
|
|
5716
5753
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_refresh_all_starting'));
|
|
5717
5754
|
if (record.messageId !== null) {
|
|
5718
|
-
await this.
|
|
5755
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_starting'), []);
|
|
5719
5756
|
}
|
|
5720
5757
|
const lease = await this.coordinator?.acquireAuthRefreshLease?.('auth refresh all');
|
|
5721
5758
|
if (lease && !lease.ok) {
|
|
5722
5759
|
if (record.messageId !== null) {
|
|
5723
|
-
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));
|
|
5724
5761
|
}
|
|
5725
5762
|
return;
|
|
5726
5763
|
}
|
|
@@ -5736,7 +5773,7 @@ export class BridgeSessionCore {
|
|
|
5736
5773
|
record.candidates = state.candidates;
|
|
5737
5774
|
record.createdAt = Date.now();
|
|
5738
5775
|
if (record.messageId !== null) {
|
|
5739
|
-
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));
|
|
5740
5777
|
}
|
|
5741
5778
|
return;
|
|
5742
5779
|
}
|
|
@@ -5773,7 +5810,7 @@ export class BridgeSessionCore {
|
|
|
5773
5810
|
clampCodexAuthListOffset(record);
|
|
5774
5811
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'decision_recorded'));
|
|
5775
5812
|
if (record.messageId !== null) {
|
|
5776
|
-
await this.
|
|
5813
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
|
|
5777
5814
|
}
|
|
5778
5815
|
}
|
|
5779
5816
|
async handleAuthRepairMenuCallback(event, localId, index, locale) {
|
|
@@ -5793,7 +5830,7 @@ export class BridgeSessionCore {
|
|
|
5793
5830
|
}
|
|
5794
5831
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_repair_actions_short'));
|
|
5795
5832
|
if (record.messageId !== null) {
|
|
5796
|
-
await this.
|
|
5833
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_repair_actions_message', { value: formatCodexAuthCandidateDisplayName(candidate.name) }), authRepairKeyboard(locale, record, index));
|
|
5797
5834
|
}
|
|
5798
5835
|
}
|
|
5799
5836
|
async handleAuthRepairActionCallback(event, localId, action, index, locale) {
|
|
@@ -5818,7 +5855,7 @@ export class BridgeSessionCore {
|
|
|
5818
5855
|
record.createdAt = Date.now();
|
|
5819
5856
|
clampCodexAuthListOffset(record);
|
|
5820
5857
|
if (record.messageId !== null) {
|
|
5821
|
-
await this.
|
|
5858
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
|
|
5822
5859
|
}
|
|
5823
5860
|
return;
|
|
5824
5861
|
}
|
|
@@ -5829,7 +5866,7 @@ export class BridgeSessionCore {
|
|
|
5829
5866
|
if (action === 'login') {
|
|
5830
5867
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'login_device_started'));
|
|
5831
5868
|
if (record.messageId !== null) {
|
|
5832
|
-
await this.
|
|
5869
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_repair_login_preparing', { value: candidate.name }), []);
|
|
5833
5870
|
}
|
|
5834
5871
|
await this.startAuthRepairLogin(event.scopeId, locale, candidate);
|
|
5835
5872
|
return;
|
|
@@ -5841,7 +5878,7 @@ export class BridgeSessionCore {
|
|
|
5841
5878
|
record.createdAt = Date.now();
|
|
5842
5879
|
clampCodexAuthListOffset(record);
|
|
5843
5880
|
if (record.messageId !== null) {
|
|
5844
|
-
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));
|
|
5845
5882
|
}
|
|
5846
5883
|
}
|
|
5847
5884
|
async handleAuthToggleCallback(event, localId, index, locale) {
|
|
@@ -5866,7 +5903,7 @@ export class BridgeSessionCore {
|
|
|
5866
5903
|
clampCodexAuthListOffset(record);
|
|
5867
5904
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, disabled ? 'auth_candidate_disabled_short' : 'auth_candidate_enabled_short'));
|
|
5868
5905
|
if (record.messageId !== null) {
|
|
5869
|
-
await this.
|
|
5906
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
|
|
5870
5907
|
}
|
|
5871
5908
|
}
|
|
5872
5909
|
async handleAuthSwitchCallback(event, localId, index, locale) {
|
|
@@ -5896,7 +5933,7 @@ export class BridgeSessionCore {
|
|
|
5896
5933
|
const switchLabels = await this.readCodexAuthSwitchLabels(candidate);
|
|
5897
5934
|
const switchingMessage = t(locale, 'auth_switching', this.codexAuthSwitchParams(locale, switchLabels.fromLabel, switchLabels.toLabel));
|
|
5898
5935
|
if (record.messageId !== null) {
|
|
5899
|
-
await this.
|
|
5936
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, switchingMessage, []);
|
|
5900
5937
|
}
|
|
5901
5938
|
const outcome = await this.switchCodexAuthAndRestart(event.scopeId, locale, candidate, false, false);
|
|
5902
5939
|
const state = await this.listCodexAuthState();
|
|
@@ -5904,7 +5941,7 @@ export class BridgeSessionCore {
|
|
|
5904
5941
|
record.createdAt = Date.now();
|
|
5905
5942
|
clampCodexAuthListOffset(record);
|
|
5906
5943
|
if (record.messageId !== null) {
|
|
5907
|
-
await this.
|
|
5944
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, [
|
|
5908
5945
|
...this.formatAuthSwitchValidationLines(locale, outcome),
|
|
5909
5946
|
renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record),
|
|
5910
5947
|
].filter(Boolean).join('\n\n'), authChoiceKeyboard(locale, record));
|
|
@@ -6837,6 +6874,7 @@ export class BridgeSessionCore {
|
|
|
6837
6874
|
? buildThreadsKeyboard(locale, threadLikes, binding.threadId)
|
|
6838
6875
|
: buildThreadListKeyboard(locale, threadLikes, listState, binding.threadId);
|
|
6839
6876
|
await this.editHtmlMessage(scopeId, event.messageId, text, keyboard);
|
|
6877
|
+
this.scheduleStalePanelDeletion(scopeId, event.messageId);
|
|
6840
6878
|
}
|
|
6841
6879
|
let callbackText = t(locale, 'thread_opened');
|
|
6842
6880
|
if (this.config.codexAppSyncOnOpen) {
|
|
@@ -7287,9 +7325,11 @@ export class BridgeSessionCore {
|
|
|
7287
7325
|
: buildThreadListKeyboard(locale, forDisplay, presentationState, binding?.threadId ?? null);
|
|
7288
7326
|
if (messageId !== undefined) {
|
|
7289
7327
|
await this.editHtmlMessage(scopeId, messageId, text, keyboard);
|
|
7328
|
+
this.scheduleStalePanelDeletion(scopeId, messageId);
|
|
7290
7329
|
return;
|
|
7291
7330
|
}
|
|
7292
|
-
await this.sendHtmlMessage(scopeId, text, keyboard);
|
|
7331
|
+
const sentMessageId = await this.sendHtmlMessage(scopeId, text, keyboard);
|
|
7332
|
+
this.scheduleStalePanelDeletion(scopeId, sentMessageId);
|
|
7293
7333
|
}
|
|
7294
7334
|
async showModelSettingsPanel(scopeId, messageId, locale = this.localeForChat(scopeId)) {
|
|
7295
7335
|
const models = await this.app.listModels();
|
|
@@ -7319,9 +7359,11 @@ export class BridgeSessionCore {
|
|
|
7319
7359
|
const keyboard = buildSetupPanelKeyboard(locale, { focus, models, settings, access });
|
|
7320
7360
|
if (messageId !== undefined) {
|
|
7321
7361
|
await this.editHtmlMessage(scopeId, messageId, text, keyboard);
|
|
7362
|
+
this.scheduleStalePanelDeletion(scopeId, messageId);
|
|
7322
7363
|
return;
|
|
7323
7364
|
}
|
|
7324
|
-
await this.sendHtmlMessage(scopeId, text, keyboard);
|
|
7365
|
+
const sentMessageId = await this.sendHtmlMessage(scopeId, text, keyboard);
|
|
7366
|
+
this.scheduleStalePanelDeletion(scopeId, sentMessageId);
|
|
7325
7367
|
}
|
|
7326
7368
|
async showAccessSettingsPanel(scopeId, messageId, locale = this.localeForChat(scopeId)) {
|
|
7327
7369
|
const access = this.resolveEffectiveAccess(scopeId);
|
|
@@ -7758,6 +7800,7 @@ export class BridgeSessionCore {
|
|
|
7758
7800
|
async attachRecoveredTurnPreview(preview, target, snapshot, liveTurn) {
|
|
7759
7801
|
await this.stopWatchingScopeThread(preview.scopeId, preview.threadId);
|
|
7760
7802
|
const active = this.createActiveTurnState(preview.scopeId, target.chatId, target.chatType, target.topicId, preview.threadId, liveTurn.turnId, preview.messageId, preview.isObserved);
|
|
7803
|
+
active.archivedMessageIds = [...preview.archivedMessageIds];
|
|
7761
7804
|
this.setActiveTurn(preview.scopeId, liveTurn.turnId, active);
|
|
7762
7805
|
this.store.saveActiveTurnPreview({
|
|
7763
7806
|
turnId: liveTurn.turnId,
|
|
@@ -7765,6 +7808,7 @@ export class BridgeSessionCore {
|
|
|
7765
7808
|
threadId: preview.threadId,
|
|
7766
7809
|
messageId: preview.messageId,
|
|
7767
7810
|
isObserved: preview.isObserved,
|
|
7811
|
+
archivedMessageIds: active.archivedMessageIds,
|
|
7768
7812
|
});
|
|
7769
7813
|
const watcher = {
|
|
7770
7814
|
scopeId: preview.scopeId,
|
|
@@ -7833,7 +7877,7 @@ export class BridgeSessionCore {
|
|
|
7833
7877
|
topicId: target.topicId,
|
|
7834
7878
|
collaborationMode: turnState.collaborationMode,
|
|
7835
7879
|
failedAuthTargets: new Set(),
|
|
7836
|
-
}, turnState.collaborationMode);
|
|
7880
|
+
}, turnState.collaborationMode, null, preview.archivedMessageIds);
|
|
7837
7881
|
this.logger.info('telegram.preview_auto_resumed_after_restart', {
|
|
7838
7882
|
scopeId: preview.scopeId,
|
|
7839
7883
|
threadId: preview.threadId,
|
|
@@ -8042,6 +8086,7 @@ export class BridgeSessionCore {
|
|
|
8042
8086
|
threadId: active.threadId,
|
|
8043
8087
|
messageId,
|
|
8044
8088
|
isObserved: active.isObserved,
|
|
8089
|
+
archivedMessageIds: active.archivedMessageIds,
|
|
8045
8090
|
});
|
|
8046
8091
|
}
|
|
8047
8092
|
catch (error) {
|
|
@@ -8999,12 +9044,14 @@ function parseRichAuthCandidateRow(line) {
|
|
|
8999
9044
|
if (!name) {
|
|
9000
9045
|
return null;
|
|
9001
9046
|
}
|
|
9002
|
-
const quotas = parts.map(
|
|
9047
|
+
const quotas = parts.map(parseRichAuthQuotaCell);
|
|
9003
9048
|
const statusParts = splitRichAuthStatus(status);
|
|
9004
9049
|
return {
|
|
9005
9050
|
index: match[1],
|
|
9006
|
-
quotaA: quotas[0] ?? '-',
|
|
9007
|
-
|
|
9051
|
+
quotaA: quotas[0]?.value ?? '-',
|
|
9052
|
+
quotaAReset: quotas[0]?.reset ?? '-',
|
|
9053
|
+
quotaB: quotas[1]?.value ?? '-',
|
|
9054
|
+
quotaBReset: quotas[1]?.reset ?? '-',
|
|
9008
9055
|
name,
|
|
9009
9056
|
current,
|
|
9010
9057
|
enabled: statusParts.health === 'disabled' || statusParts.health === '已禁用'
|
|
@@ -9016,12 +9063,14 @@ function parseRichAuthCandidateRow(line) {
|
|
|
9016
9063
|
function formatRichAuthCandidateTable(rows) {
|
|
9017
9064
|
return [
|
|
9018
9065
|
'<table bordered striped>',
|
|
9019
|
-
'<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>',
|
|
9020
9067
|
...rows.map(row => [
|
|
9021
9068
|
'<tr>',
|
|
9022
9069
|
`<td>${escapeTelegramHtml(row.index)}</td>`,
|
|
9023
9070
|
`<td>${escapeTelegramHtml(row.quotaA)}</td>`,
|
|
9071
|
+
`<td>${escapeTelegramHtml(row.quotaAReset)}</td>`,
|
|
9024
9072
|
`<td>${escapeTelegramHtml(row.quotaB)}</td>`,
|
|
9073
|
+
`<td>${escapeTelegramHtml(row.quotaBReset)}</td>`,
|
|
9025
9074
|
`<td>${formatRichAuthCommandLink(`/auth use ${row.index}`, row.name)}</td>`,
|
|
9026
9075
|
`<td>${row.current ? 'yes' : '-'}</td>`,
|
|
9027
9076
|
`<td>${escapeTelegramHtml(row.plan)}</td>`,
|
|
@@ -9072,16 +9121,17 @@ function formatRichAuthRisk(health) {
|
|
|
9072
9121
|
}
|
|
9073
9122
|
return '-';
|
|
9074
9123
|
}
|
|
9075
|
-
function
|
|
9124
|
+
function parseRichAuthQuotaCell(value) {
|
|
9076
9125
|
const trimmed = value.trim();
|
|
9077
9126
|
if (!trimmed || trimmed === '--' || trimmed === '—') {
|
|
9078
|
-
return '-';
|
|
9127
|
+
return { value: '-', reset: '-' };
|
|
9079
9128
|
}
|
|
9080
|
-
const [
|
|
9129
|
+
const [quotaPart, reset = '-'] = trimmed.split('@', 2);
|
|
9130
|
+
const [windowLabel, percent] = quotaPart.split(':');
|
|
9081
9131
|
if (!windowLabel || percent === undefined) {
|
|
9082
|
-
return
|
|
9132
|
+
return { value: quotaPart, reset };
|
|
9083
9133
|
}
|
|
9084
|
-
return `${percent}
|
|
9134
|
+
return { value: `${percent}%`, reset };
|
|
9085
9135
|
}
|
|
9086
9136
|
function formatRichAuthCommandCell(row) {
|
|
9087
9137
|
const commands = [formatRichAuthCommandLink(`/auth use ${row.index}`, 'use')];
|
|
@@ -11130,8 +11180,10 @@ function authQuotaSnapshotFromRateLimit(snapshot, accountId = null, quotaIdentit
|
|
|
11130
11180
|
planType: snapshot.planType,
|
|
11131
11181
|
primaryWindowDurationMins: snapshot.primary?.windowDurationMins ?? null,
|
|
11132
11182
|
primaryRemainingPercent: snapshot.primary ? remainingUsagePercent(snapshot.primary.usedPercent) : null,
|
|
11183
|
+
primaryResetsAt: snapshot.primary?.resetsAt ?? null,
|
|
11133
11184
|
secondaryWindowDurationMins: snapshot.secondary?.windowDurationMins ?? null,
|
|
11134
11185
|
secondaryRemainingPercent: snapshot.secondary ? remainingUsagePercent(snapshot.secondary.usedPercent) : null,
|
|
11186
|
+
secondaryResetsAt: snapshot.secondary?.resetsAt ?? null,
|
|
11135
11187
|
};
|
|
11136
11188
|
}
|
|
11137
11189
|
function codexAuthQuotaSnapshotFromRecord(record) {
|
|
@@ -11142,8 +11194,10 @@ function codexAuthQuotaSnapshotFromRecord(record) {
|
|
|
11142
11194
|
planType: record.planType,
|
|
11143
11195
|
primaryWindowDurationMins: record.primaryWindowDurationMins,
|
|
11144
11196
|
primaryRemainingPercent: record.primaryRemainingPercent,
|
|
11197
|
+
primaryResetsAt: record.primaryResetsAt,
|
|
11145
11198
|
secondaryWindowDurationMins: record.secondaryWindowDurationMins,
|
|
11146
11199
|
secondaryRemainingPercent: record.secondaryRemainingPercent,
|
|
11200
|
+
secondaryResetsAt: record.secondaryResetsAt,
|
|
11147
11201
|
};
|
|
11148
11202
|
}
|
|
11149
11203
|
function mergeCodexAuthQuotaSnapshots(current, incoming) {
|
|
@@ -11166,8 +11220,10 @@ function isFiniteCodexAuthQuotaSnapshotRecord(record) {
|
|
|
11166
11220
|
&& isNullableString(record.planType)
|
|
11167
11221
|
&& isNullableFiniteNumber(record.primaryWindowDurationMins)
|
|
11168
11222
|
&& isNullableFiniteNumber(record.primaryRemainingPercent)
|
|
11223
|
+
&& isNullableFiniteNumber(record.primaryResetsAt)
|
|
11169
11224
|
&& isNullableFiniteNumber(record.secondaryWindowDurationMins)
|
|
11170
|
-
&& isNullableFiniteNumber(record.secondaryRemainingPercent)
|
|
11225
|
+
&& isNullableFiniteNumber(record.secondaryRemainingPercent)
|
|
11226
|
+
&& isNullableFiniteNumber(record.secondaryResetsAt);
|
|
11171
11227
|
}
|
|
11172
11228
|
function remainingUsagePercent(usedPercent) {
|
|
11173
11229
|
if (!Number.isFinite(usedPercent)) {
|
|
@@ -11180,12 +11236,12 @@ function formatAuthQuotaPrefix(locale, snapshot) {
|
|
|
11180
11236
|
return '--';
|
|
11181
11237
|
}
|
|
11182
11238
|
const windows = [
|
|
11183
|
-
[snapshot.primaryWindowDurationMins, snapshot.primaryRemainingPercent, 'primary'],
|
|
11184
|
-
[snapshot.secondaryWindowDurationMins, snapshot.secondaryRemainingPercent, 'secondary'],
|
|
11239
|
+
[snapshot.primaryWindowDurationMins, snapshot.primaryRemainingPercent, snapshot.primaryResetsAt, 'primary'],
|
|
11240
|
+
[snapshot.secondaryWindowDurationMins, snapshot.secondaryRemainingPercent, snapshot.secondaryResetsAt, 'secondary'],
|
|
11185
11241
|
];
|
|
11186
11242
|
const values = windows
|
|
11187
|
-
.filter(([duration, remaining]) => duration !== null || remaining !== null)
|
|
11188
|
-
.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)}`}`));
|
|
11189
11245
|
return values.length > 0 ? values.join('|') : '--';
|
|
11190
11246
|
}
|
|
11191
11247
|
function formatAuthQuotaButtonPrefix(snapshot) {
|
|
@@ -11211,8 +11267,10 @@ function isCodexAuthQuotaSnapshot(value) {
|
|
|
11211
11267
|
&& (snapshot.planType === undefined || isNullableString(snapshot.planType))
|
|
11212
11268
|
&& (snapshot.primaryWindowDurationMins === undefined || isNullableFiniteNumber(snapshot.primaryWindowDurationMins))
|
|
11213
11269
|
&& isNullableFiniteNumber(snapshot.primaryRemainingPercent)
|
|
11270
|
+
&& (snapshot.primaryResetsAt === undefined || isNullableFiniteNumber(snapshot.primaryResetsAt))
|
|
11214
11271
|
&& (snapshot.secondaryWindowDurationMins === undefined || isNullableFiniteNumber(snapshot.secondaryWindowDurationMins))
|
|
11215
|
-
&& isNullableFiniteNumber(snapshot.secondaryRemainingPercent)
|
|
11272
|
+
&& isNullableFiniteNumber(snapshot.secondaryRemainingPercent)
|
|
11273
|
+
&& (snapshot.secondaryResetsAt === undefined || isNullableFiniteNumber(snapshot.secondaryResetsAt));
|
|
11216
11274
|
}
|
|
11217
11275
|
function normalizeCodexAuthQuotaSnapshot(snapshot) {
|
|
11218
11276
|
return {
|
|
@@ -11222,8 +11280,10 @@ function normalizeCodexAuthQuotaSnapshot(snapshot) {
|
|
|
11222
11280
|
planType: snapshot.planType ?? null,
|
|
11223
11281
|
primaryWindowDurationMins: snapshot.primaryWindowDurationMins ?? null,
|
|
11224
11282
|
primaryRemainingPercent: snapshot.primaryRemainingPercent,
|
|
11283
|
+
primaryResetsAt: snapshot.primaryResetsAt ?? null,
|
|
11225
11284
|
secondaryWindowDurationMins: snapshot.secondaryWindowDurationMins ?? null,
|
|
11226
11285
|
secondaryRemainingPercent: snapshot.secondaryRemainingPercent,
|
|
11286
|
+
secondaryResetsAt: snapshot.secondaryResetsAt ?? null,
|
|
11227
11287
|
};
|
|
11228
11288
|
}
|
|
11229
11289
|
function chatGptAuthMetadataCompatible(left, right) {
|
package/dist/store/database.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface ActiveTurnPreviewRecord {
|
|
|
5
5
|
threadId: string;
|
|
6
6
|
messageId: number;
|
|
7
7
|
isObserved: boolean;
|
|
8
|
+
archivedMessageIds: number[];
|
|
8
9
|
createdAt: number;
|
|
9
10
|
updatedAt: number;
|
|
10
11
|
}
|
|
@@ -34,8 +35,10 @@ export interface CodexAuthQuotaSnapshotRecord {
|
|
|
34
35
|
planType: string | null;
|
|
35
36
|
primaryWindowDurationMins: number | null;
|
|
36
37
|
primaryRemainingPercent: number | null;
|
|
38
|
+
primaryResetsAt: number | null;
|
|
37
39
|
secondaryWindowDurationMins: number | null;
|
|
38
40
|
secondaryRemainingPercent: number | null;
|
|
41
|
+
secondaryResetsAt: number | null;
|
|
39
42
|
updatedAt: number;
|
|
40
43
|
}
|
|
41
44
|
export interface CodexAuthPoolStats {
|
|
@@ -82,6 +85,7 @@ export declare class BridgeStore {
|
|
|
82
85
|
countPendingApprovals(): number;
|
|
83
86
|
saveActiveTurnPreview(record: Pick<ActiveTurnPreviewRecord, 'turnId' | 'scopeId' | 'threadId' | 'messageId'> & {
|
|
84
87
|
isObserved?: boolean;
|
|
88
|
+
archivedMessageIds?: number[];
|
|
85
89
|
}): void;
|
|
86
90
|
listActiveTurnPreviews(): ActiveTurnPreviewRecord[];
|
|
87
91
|
removeActiveTurnPreview(turnId: string): void;
|
|
@@ -134,7 +138,7 @@ export declare class BridgeStore {
|
|
|
134
138
|
recordCodexAuthCandidateInvalidDelete(name: string, reason?: string | null): void;
|
|
135
139
|
recordCodexAuthCandidateRemoved(name: string, reason?: string | null): void;
|
|
136
140
|
getCodexAuthPoolStats(): CodexAuthPoolStats;
|
|
137
|
-
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;
|
|
138
142
|
listCodexAuthQuotaSnapshots(quotaIdentityIds: string[]): CodexAuthQuotaSnapshotRecord[];
|
|
139
143
|
private ensureColumn;
|
|
140
144
|
}
|
package/dist/store/database.js
CHANGED
|
@@ -71,6 +71,7 @@ export class BridgeStore {
|
|
|
71
71
|
thread_id TEXT NOT NULL,
|
|
72
72
|
message_id INTEGER NOT NULL,
|
|
73
73
|
is_observed INTEGER NOT NULL DEFAULT 0,
|
|
74
|
+
archived_message_ids TEXT NOT NULL DEFAULT '[]',
|
|
74
75
|
created_at INTEGER NOT NULL,
|
|
75
76
|
updated_at INTEGER NOT NULL
|
|
76
77
|
);
|
|
@@ -190,8 +191,10 @@ export class BridgeStore {
|
|
|
190
191
|
plan_type TEXT,
|
|
191
192
|
primary_window_duration_mins REAL,
|
|
192
193
|
primary_remaining_percent REAL,
|
|
194
|
+
primary_resets_at INTEGER,
|
|
193
195
|
secondary_window_duration_mins REAL,
|
|
194
196
|
secondary_remaining_percent REAL,
|
|
197
|
+
secondary_resets_at INTEGER,
|
|
195
198
|
updated_at INTEGER NOT NULL,
|
|
196
199
|
PRIMARY KEY (runtime_id, candidate_name)
|
|
197
200
|
);
|
|
@@ -219,11 +222,14 @@ export class BridgeStore {
|
|
|
219
222
|
this.ensureColumn('pending_user_inputs', 'status', "TEXT NOT NULL DEFAULT 'pending'");
|
|
220
223
|
this.ensureColumn('pending_user_inputs', 'submitted_at', 'INTEGER');
|
|
221
224
|
this.ensureColumn('active_turn_previews', 'is_observed', 'INTEGER NOT NULL DEFAULT 0');
|
|
225
|
+
this.ensureColumn('active_turn_previews', 'archived_message_ids', "TEXT NOT NULL DEFAULT '[]'");
|
|
222
226
|
this.ensureColumn('codex_auth_candidates', 'state', "TEXT NOT NULL DEFAULT 'active'");
|
|
223
227
|
this.ensureColumn('codex_auth_candidate_runtime', 'state', "TEXT NOT NULL DEFAULT 'active'");
|
|
224
228
|
this.ensureColumn('codex_auth_quota_snapshots', 'plan_type', 'TEXT');
|
|
225
229
|
this.ensureColumn('codex_auth_quota_snapshots', 'primary_window_duration_mins', 'REAL');
|
|
230
|
+
this.ensureColumn('codex_auth_quota_snapshots', 'primary_resets_at', 'INTEGER');
|
|
226
231
|
this.ensureColumn('codex_auth_quota_snapshots', 'secondary_window_duration_mins', 'REAL');
|
|
232
|
+
this.ensureColumn('codex_auth_quota_snapshots', 'secondary_resets_at', 'INTEGER');
|
|
227
233
|
this.ensureColumn('codex_auth_quota_snapshots', 'quota_identity_id', "TEXT NOT NULL DEFAULT ''");
|
|
228
234
|
this.ensureColumn('codex_auth_pool_history', 'invalid_delete_count', 'INTEGER NOT NULL DEFAULT 0');
|
|
229
235
|
this.db.prepare(`
|
|
@@ -426,13 +432,13 @@ export class BridgeStore {
|
|
|
426
432
|
const now = Date.now();
|
|
427
433
|
this.db.prepare('DELETE FROM active_turn_previews WHERE turn_id = ? OR scope_id = ?').run(record.turnId, record.scopeId);
|
|
428
434
|
this.db.prepare(`
|
|
429
|
-
INSERT INTO active_turn_previews (turn_id, scope_id, thread_id, message_id, is_observed, created_at, updated_at)
|
|
430
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
431
|
-
`).run(record.turnId, record.scopeId, record.threadId, record.messageId, record.isObserved ? 1 : 0, now, now);
|
|
435
|
+
INSERT INTO active_turn_previews (turn_id, scope_id, thread_id, message_id, is_observed, archived_message_ids, created_at, updated_at)
|
|
436
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
437
|
+
`).run(record.turnId, record.scopeId, record.threadId, record.messageId, record.isObserved ? 1 : 0, JSON.stringify(normalizeMessageIds(record.archivedMessageIds ?? [])), now, now);
|
|
432
438
|
}
|
|
433
439
|
listActiveTurnPreviews() {
|
|
434
440
|
const rows = this.db.prepare(`
|
|
435
|
-
SELECT turn_id, scope_id, thread_id, message_id, is_observed, created_at, updated_at
|
|
441
|
+
SELECT turn_id, scope_id, thread_id, message_id, is_observed, archived_message_ids, created_at, updated_at
|
|
436
442
|
FROM active_turn_previews
|
|
437
443
|
ORDER BY created_at ASC
|
|
438
444
|
`).all();
|
|
@@ -442,6 +448,7 @@ export class BridgeStore {
|
|
|
442
448
|
threadId: String(row.thread_id),
|
|
443
449
|
messageId: Number(row.message_id),
|
|
444
450
|
isObserved: Boolean(row.is_observed),
|
|
451
|
+
archivedMessageIds: parseMessageIdArray(row.archived_message_ids),
|
|
445
452
|
createdAt: Number(row.created_at),
|
|
446
453
|
updatedAt: Number(row.updated_at),
|
|
447
454
|
}));
|
|
@@ -994,11 +1001,13 @@ export class BridgeStore {
|
|
|
994
1001
|
plan_type,
|
|
995
1002
|
primary_window_duration_mins,
|
|
996
1003
|
primary_remaining_percent,
|
|
1004
|
+
primary_resets_at,
|
|
997
1005
|
secondary_window_duration_mins,
|
|
998
1006
|
secondary_remaining_percent,
|
|
1007
|
+
secondary_resets_at,
|
|
999
1008
|
updated_at
|
|
1000
1009
|
)
|
|
1001
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1010
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1002
1011
|
ON CONFLICT(runtime_id, candidate_name) DO UPDATE SET
|
|
1003
1012
|
account_id = excluded.account_id,
|
|
1004
1013
|
quota_identity_id = excluded.quota_identity_id,
|
|
@@ -1006,10 +1015,12 @@ export class BridgeStore {
|
|
|
1006
1015
|
plan_type = excluded.plan_type,
|
|
1007
1016
|
primary_window_duration_mins = excluded.primary_window_duration_mins,
|
|
1008
1017
|
primary_remaining_percent = excluded.primary_remaining_percent,
|
|
1018
|
+
primary_resets_at = excluded.primary_resets_at,
|
|
1009
1019
|
secondary_window_duration_mins = excluded.secondary_window_duration_mins,
|
|
1010
1020
|
secondary_remaining_percent = excluded.secondary_remaining_percent,
|
|
1021
|
+
secondary_resets_at = excluded.secondary_resets_at,
|
|
1011
1022
|
updated_at = excluded.updated_at
|
|
1012
|
-
`).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());
|
|
1013
1024
|
}
|
|
1014
1025
|
listCodexAuthQuotaSnapshots(quotaIdentityIds) {
|
|
1015
1026
|
const uniqueQuotaIdentityIds = [...new Set(quotaIdentityIds.filter(Boolean))];
|
|
@@ -1027,8 +1038,10 @@ export class BridgeStore {
|
|
|
1027
1038
|
plan_type,
|
|
1028
1039
|
primary_window_duration_mins,
|
|
1029
1040
|
primary_remaining_percent,
|
|
1041
|
+
primary_resets_at,
|
|
1030
1042
|
secondary_window_duration_mins,
|
|
1031
1043
|
secondary_remaining_percent,
|
|
1044
|
+
secondary_resets_at,
|
|
1032
1045
|
updated_at
|
|
1033
1046
|
FROM codex_auth_quota_snapshots
|
|
1034
1047
|
WHERE quota_identity_id IN (${placeholders})
|
|
@@ -1042,8 +1055,10 @@ export class BridgeStore {
|
|
|
1042
1055
|
planType: nullableString(row.plan_type),
|
|
1043
1056
|
primaryWindowDurationMins: nullableNumber(row.primary_window_duration_mins),
|
|
1044
1057
|
primaryRemainingPercent: nullableNumber(row.primary_remaining_percent),
|
|
1058
|
+
primaryResetsAt: nullableNumber(row.primary_resets_at),
|
|
1045
1059
|
secondaryWindowDurationMins: nullableNumber(row.secondary_window_duration_mins),
|
|
1046
1060
|
secondaryRemainingPercent: nullableNumber(row.secondary_remaining_percent),
|
|
1061
|
+
secondaryResetsAt: nullableNumber(row.secondary_resets_at),
|
|
1047
1062
|
updatedAt: Number(row.updated_at),
|
|
1048
1063
|
}));
|
|
1049
1064
|
}
|
|
@@ -1065,6 +1080,23 @@ function nullableNumber(value) {
|
|
|
1065
1080
|
function nullableString(value) {
|
|
1066
1081
|
return typeof value === 'string' && value.trim() ? value : null;
|
|
1067
1082
|
}
|
|
1083
|
+
function normalizeMessageIds(values) {
|
|
1084
|
+
return [...new Set(values.filter((value) => Number.isSafeInteger(value) && value > 0))];
|
|
1085
|
+
}
|
|
1086
|
+
function parseMessageIdArray(value) {
|
|
1087
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
1088
|
+
return [];
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
const parsed = JSON.parse(value);
|
|
1092
|
+
return Array.isArray(parsed)
|
|
1093
|
+
? normalizeMessageIds(parsed.map((entry) => Number(entry)))
|
|
1094
|
+
: [];
|
|
1095
|
+
}
|
|
1096
|
+
catch {
|
|
1097
|
+
return [];
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1068
1100
|
function normalizeCodexAuthCandidateState(value) {
|
|
1069
1101
|
return value === 'needs_repair' ? 'needs_repair' : 'active';
|
|
1070
1102
|
}
|