@foxden-app/foxclaw 0.5.58 → 0.5.61

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 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, /setup, and /threads after 5 minutes.
42
+ # The timer restarts whenever the panel is refreshed. Set to 0 to disable.
43
+ # TELEGRAM_PANEL_TTL_MS=300000
41
44
  THREAD_LIST_LIMIT=10
42
45
  CODEX_CLI_BIN=/absolute/path/to/codex
43
46
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,38 @@
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.61 - 2026-06-21
6
+
7
+ ### 中文
8
+ - 将 `/auth`、`/setup`、`/threads` 等交互面板的默认自动清理时间从 30 分钟改为 5 分钟,并把 `/where`、旧 model/access 设置面板和 `/config` 面板纳入同一类时效清理。
9
+ - `/config` 现在明确定位为 FoxClaw 自身设置面板,新增“最终回复后删除操作明细”开关;可通过按钮或 `/config delete_tool_details <on|off>` 修改,并写回 `TELEGRAM_DELETE_TOOL_DETAILS_AFTER_FINAL`。
10
+ - `/config` 展示当前 `TELEGRAM_DELETE_TOOL_DETAILS_AFTER_FINAL` 和 `TELEGRAM_PANEL_TTL_MS`,方便确认聊天清理策略是否生效。
11
+
12
+ ### English
13
+ - Changed the default cleanup timeout for interactive panels such as `/auth`, `/setup`, and `/threads` from 30 minutes to 5 minutes, and applied the same stale-panel cleanup to `/where`, legacy model/access settings panels, and `/config`.
14
+ - `/config` is now the FoxClaw runtime settings panel and includes a "delete operation details after final reply" toggle. It can be changed via the button or `/config delete_tool_details <on|off>`, writing `TELEGRAM_DELETE_TOOL_DETAILS_AFTER_FINAL`.
15
+ - `/config` shows the current `TELEGRAM_DELETE_TOOL_DETAILS_AFTER_FINAL` and `TELEGRAM_PANEL_TTL_MS` values so cleanup behavior is visible.
16
+
17
+ ## 0.5.60 - 2026-06-21
18
+
19
+ ### 中文
20
+ - 修复 `/auth` 刷新时间测试对 Asia/Shanghai 时区的硬编码,使 Linux、macOS 和 UTC CI runner 都按各自本地时区验证;功能内容与 0.5.59 一并发布。
21
+
22
+ ### English
23
+ - 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.
24
+
25
+ ## 0.5.59 - 2026-06-21
26
+
27
+ ### 中文
28
+ - `/auth` 富文本表格新增 Quota A、Quota B 的下次刷新时间;精确时间随额度快照持久化并可在节点间共享,旧快照安全显示 `-`。
29
+ - 修复 `/auth` 按钮回调后退化为普通消息的问题:切换、启停、筛选、翻页、修复、刷新和安全同步现在始终使用 RichMessage 编辑。
30
+ - 新增时效面板自动清理:`/auth`、`/threads` 和统一 `/setup` 面板默认空闲 30 分钟后删除,每次交互重新计时;可用 `TELEGRAM_PANEL_TTL_MS` 调整,设为 `0` 可关闭。
31
+
32
+ ### English
33
+ - 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 `-`.
34
+ - 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.
35
+ - 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.
36
+
5
37
  ## 0.5.58 - 2026-06-21
6
38
 
7
39
  ### 中文
package/dist/config.d.ts CHANGED
@@ -41,6 +41,7 @@ export interface AppConfig {
41
41
  telegramPollIntervalMs: number;
42
42
  telegramPreviewThrottleMs: number;
43
43
  telegramDeleteToolDetailsAfterFinal: boolean;
44
+ telegramPanelTtlMs: number;
44
45
  threadListLimit: number;
45
46
  statusPath: string;
46
47
  logPath: string;
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', 5 * 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;
@@ -348,7 +352,7 @@ export declare class BridgeSessionCore {
348
352
  private handleFeaturesCommand;
349
353
  private handleConfigCommand;
350
354
  private handleConfigToggleCallback;
351
- private setAuthAutoDeleteNeedsRepair;
355
+ private setFoxClawBooleanConfig;
352
356
  private formatConfigToggleUpdate;
353
357
  private handleRequirementsCommand;
354
358
  private handleProviderCommand;
@@ -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
  }
@@ -1083,9 +1088,9 @@ export class BridgeSessionCore {
1083
1088
  await this.handleSetupCallback(event, setupMatch[1], setupMatch[2], locale);
1084
1089
  return;
1085
1090
  }
1086
- const configMatch = /^config:auth_auto_delete:(on|off)$/.exec(event.data);
1091
+ const configMatch = /^config:(auth_auto_delete|delete_tool_details):(on|off)$/.exec(event.data);
1087
1092
  if (configMatch) {
1088
- await this.handleConfigToggleCallback(event, configMatch[1] === 'on', locale);
1093
+ await this.handleConfigToggleCallback(event, configMatch[1], configMatch[2] === 'on', locale);
1089
1094
  return;
1090
1095
  }
1091
1096
  const voiceMatch = /^voice:([a-f0-9]+)$/.exec(event.data);
@@ -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';
@@ -5548,46 +5583,73 @@ export class BridgeSessionCore {
5548
5583
  await this.sendMessage(scopeId, t(locale, 'config_auth_auto_delete_usage'));
5549
5584
  return;
5550
5585
  }
5551
- const update = await this.setAuthAutoDeleteNeedsRepair(enabled);
5586
+ const update = await this.setFoxClawBooleanConfig('auth_auto_delete', enabled);
5587
+ const binding = this.store.getBinding(scopeId);
5588
+ const result = await this.app.readConfig(binding?.cwd ?? this.config.defaultCwd, true);
5589
+ const sentMessageId = await this.sendMessage(scopeId, `${this.formatConfigToggleUpdate(locale, update)}\n\n${formatConfigMessage(locale, result, this.config, this.store.getCodexAuthPoolStats())}`, configKeyboard(locale, this.config));
5590
+ this.scheduleStalePanelDeletion(scopeId, sentMessageId);
5591
+ return;
5592
+ }
5593
+ if (['delete_tool_details', 'delete-tool-details', 'tool_details', 'tool-details'].includes(action)) {
5594
+ const enabled = parseConfigBooleanArg(args[1]);
5595
+ if (enabled === null) {
5596
+ await this.sendMessage(scopeId, t(locale, 'config_delete_tool_details_usage'));
5597
+ return;
5598
+ }
5599
+ const update = await this.setFoxClawBooleanConfig('delete_tool_details', enabled);
5552
5600
  const binding = this.store.getBinding(scopeId);
5553
5601
  const result = await this.app.readConfig(binding?.cwd ?? this.config.defaultCwd, true);
5554
- await this.sendMessage(scopeId, `${this.formatConfigToggleUpdate(locale, update)}\n\n${formatConfigMessage(locale, result, this.config, this.store.getCodexAuthPoolStats())}`, configKeyboard(locale, this.config));
5602
+ const sentMessageId = await this.sendMessage(scopeId, `${this.formatConfigToggleUpdate(locale, update)}\n\n${formatConfigMessage(locale, result, this.config, this.store.getCodexAuthPoolStats())}`, configKeyboard(locale, this.config));
5603
+ this.scheduleStalePanelDeletion(scopeId, sentMessageId);
5555
5604
  return;
5556
5605
  }
5557
5606
  const binding = this.store.getBinding(scopeId);
5558
5607
  const result = await this.app.readConfig(binding?.cwd ?? this.config.defaultCwd, true);
5559
- await this.sendMessage(scopeId, formatConfigMessage(locale, result, this.config, this.store.getCodexAuthPoolStats()), configKeyboard(locale, this.config));
5608
+ const sentMessageId = await this.sendMessage(scopeId, formatConfigMessage(locale, result, this.config, this.store.getCodexAuthPoolStats()), configKeyboard(locale, this.config));
5609
+ this.scheduleStalePanelDeletion(scopeId, sentMessageId);
5560
5610
  }
5561
- async handleConfigToggleCallback(event, enabled, locale) {
5562
- const update = await this.setAuthAutoDeleteNeedsRepair(enabled);
5611
+ async handleConfigToggleCallback(event, key, enabled, locale) {
5612
+ const update = await this.setFoxClawBooleanConfig(key, enabled);
5563
5613
  await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'decision_recorded'));
5564
5614
  const binding = this.store.getBinding(event.scopeId);
5565
5615
  const result = await this.app.readConfig(binding?.cwd ?? this.config.defaultCwd, true);
5566
5616
  const message = `${this.formatConfigToggleUpdate(locale, update)}\n\n${formatConfigMessage(locale, result, this.config, this.store.getCodexAuthPoolStats())}`;
5567
5617
  if (event.messageId !== null) {
5568
5618
  await this.editMessage(event.scopeId, event.messageId, message, configKeyboard(locale, this.config));
5619
+ this.scheduleStalePanelDeletion(event.scopeId, event.messageId);
5569
5620
  }
5570
5621
  else {
5571
- await this.sendMessage(event.scopeId, message, configKeyboard(locale, this.config));
5622
+ const sentMessageId = await this.sendMessage(event.scopeId, message, configKeyboard(locale, this.config));
5623
+ this.scheduleStalePanelDeletion(event.scopeId, sentMessageId);
5572
5624
  }
5573
5625
  }
5574
- async setAuthAutoDeleteNeedsRepair(enabled) {
5575
- this.config.authAutoDeleteNeedsRepair = enabled;
5626
+ async setFoxClawBooleanConfig(key, enabled) {
5627
+ const envKey = key === 'auth_auto_delete'
5628
+ ? 'AUTH_AUTO_DELETE_NEEDS_REPAIR'
5629
+ : 'TELEGRAM_DELETE_TOOL_DETAILS_AFTER_FINAL';
5630
+ if (key === 'auth_auto_delete') {
5631
+ this.config.authAutoDeleteNeedsRepair = enabled;
5632
+ }
5633
+ else {
5634
+ this.config.telegramDeleteToolDetailsAfterFinal = enabled;
5635
+ }
5576
5636
  const envPath = this.config.envPath;
5577
5637
  if (!envPath) {
5578
- return { enabled, envPath: null, envUpdated: false, envError: null };
5638
+ return { key, enabled, envKey, envPath: null, envUpdated: false, envError: null };
5579
5639
  }
5580
5640
  try {
5581
- await writeEnvBoolean(envPath, 'AUTH_AUTO_DELETE_NEEDS_REPAIR', enabled);
5582
- return { enabled, envPath, envUpdated: true, envError: null };
5641
+ await writeEnvBoolean(envPath, envKey, enabled);
5642
+ return { key, enabled, envKey, envPath, envUpdated: true, envError: null };
5583
5643
  }
5584
5644
  catch (error) {
5585
- this.logger.warn('config.env_update_failed', { key: 'AUTH_AUTO_DELETE_NEEDS_REPAIR', envPath, error: toErrorMeta(error) });
5586
- return { enabled, envPath, envUpdated: false, envError: formatUserError(error) };
5645
+ this.logger.warn('config.env_update_failed', { key: envKey, envPath, error: toErrorMeta(error) });
5646
+ return { key, enabled, envKey, envPath, envUpdated: false, envError: formatUserError(error) };
5587
5647
  }
5588
5648
  }
5589
5649
  formatConfigToggleUpdate(locale, update) {
5590
- const lines = [t(locale, 'config_auth_auto_delete_updated', { value: t(locale, update.enabled ? 'yes' : 'no') })];
5650
+ const lines = [t(locale, update.key === 'auth_auto_delete' ? 'config_auth_auto_delete_updated' : 'config_delete_tool_details_updated', {
5651
+ value: t(locale, update.enabled ? 'yes' : 'no'),
5652
+ })];
5591
5653
  if (update.envError) {
5592
5654
  lines.push(t(locale, 'config_env_update_failed', { value: update.envPath ?? t(locale, 'unknown'), error: update.envError }));
5593
5655
  }
@@ -5664,12 +5726,12 @@ export class BridgeSessionCore {
5664
5726
  }
5665
5727
  await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_sync_safe_starting'));
5666
5728
  if (record.messageId !== null) {
5667
- await this.editMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_safe_starting'), []);
5729
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_safe_starting'), []);
5668
5730
  }
5669
5731
  const result = await this.runAuthSafeSyncAll();
5670
5732
  if (!result) {
5671
5733
  if (record.messageId !== null) {
5672
- await this.editMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_disabled'), authChoiceKeyboard(locale, record));
5734
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_disabled'), authChoiceKeyboard(locale, record));
5673
5735
  }
5674
5736
  return;
5675
5737
  }
@@ -5679,7 +5741,7 @@ export class BridgeSessionCore {
5679
5741
  record.createdAt = Date.now();
5680
5742
  clampCodexAuthListOffset(record);
5681
5743
  if (record.messageId !== null) {
5682
- await this.editMessage(event.scopeId, record.messageId, `${t(locale, 'auth_sync_safe_done', {
5744
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, `${t(locale, 'auth_sync_safe_done', {
5683
5745
  localSynced: result.localSynced,
5684
5746
  localSkipped: result.localSkipped,
5685
5747
  sent: result.sent,
@@ -5695,7 +5757,7 @@ export class BridgeSessionCore {
5695
5757
  }
5696
5758
  await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_refresh_all_confirm_short'));
5697
5759
  if (record.messageId !== null) {
5698
- await this.editMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_confirm_message'), authRefreshAllConfirmKeyboard(locale, record));
5760
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_confirm_message'), authRefreshAllConfirmKeyboard(locale, record));
5699
5761
  }
5700
5762
  return;
5701
5763
  }
@@ -5706,7 +5768,7 @@ export class BridgeSessionCore {
5706
5768
  record.candidates = state.candidates;
5707
5769
  record.createdAt = Date.now();
5708
5770
  if (record.messageId !== null) {
5709
- await this.editMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
5771
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
5710
5772
  }
5711
5773
  return;
5712
5774
  }
@@ -5717,12 +5779,12 @@ export class BridgeSessionCore {
5717
5779
  }
5718
5780
  await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_refresh_all_starting'));
5719
5781
  if (record.messageId !== null) {
5720
- await this.editMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_starting'), []);
5782
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_starting'), []);
5721
5783
  }
5722
5784
  const lease = await this.coordinator?.acquireAuthRefreshLease?.('auth refresh all');
5723
5785
  if (lease && !lease.ok) {
5724
5786
  if (record.messageId !== null) {
5725
- await this.editMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_lease_failed', { error: lease.reason ?? t(locale, 'unknown') }), authChoiceKeyboard(locale, record));
5787
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_lease_failed', { error: lease.reason ?? t(locale, 'unknown') }), authChoiceKeyboard(locale, record));
5726
5788
  }
5727
5789
  return;
5728
5790
  }
@@ -5738,7 +5800,7 @@ export class BridgeSessionCore {
5738
5800
  record.candidates = state.candidates;
5739
5801
  record.createdAt = Date.now();
5740
5802
  if (record.messageId !== null) {
5741
- await this.editMessage(event.scopeId, record.messageId, `${formatAuthRefreshAllResult(locale, result)}\n\n${renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record)}`, authChoiceKeyboard(locale, record));
5803
+ 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
5804
  }
5743
5805
  return;
5744
5806
  }
@@ -5775,7 +5837,7 @@ export class BridgeSessionCore {
5775
5837
  clampCodexAuthListOffset(record);
5776
5838
  await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'decision_recorded'));
5777
5839
  if (record.messageId !== null) {
5778
- await this.editMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
5840
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
5779
5841
  }
5780
5842
  }
5781
5843
  async handleAuthRepairMenuCallback(event, localId, index, locale) {
@@ -5795,7 +5857,7 @@ export class BridgeSessionCore {
5795
5857
  }
5796
5858
  await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_repair_actions_short'));
5797
5859
  if (record.messageId !== null) {
5798
- await this.editMessage(event.scopeId, record.messageId, t(locale, 'auth_repair_actions_message', { value: formatCodexAuthCandidateDisplayName(candidate.name) }), authRepairKeyboard(locale, record, index));
5860
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_repair_actions_message', { value: formatCodexAuthCandidateDisplayName(candidate.name) }), authRepairKeyboard(locale, record, index));
5799
5861
  }
5800
5862
  }
5801
5863
  async handleAuthRepairActionCallback(event, localId, action, index, locale) {
@@ -5820,7 +5882,7 @@ export class BridgeSessionCore {
5820
5882
  record.createdAt = Date.now();
5821
5883
  clampCodexAuthListOffset(record);
5822
5884
  if (record.messageId !== null) {
5823
- await this.editMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
5885
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
5824
5886
  }
5825
5887
  return;
5826
5888
  }
@@ -5831,7 +5893,7 @@ export class BridgeSessionCore {
5831
5893
  if (action === 'login') {
5832
5894
  await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'login_device_started'));
5833
5895
  if (record.messageId !== null) {
5834
- await this.editMessage(event.scopeId, record.messageId, t(locale, 'auth_repair_login_preparing', { value: candidate.name }), []);
5896
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_repair_login_preparing', { value: candidate.name }), []);
5835
5897
  }
5836
5898
  await this.startAuthRepairLogin(event.scopeId, locale, candidate);
5837
5899
  return;
@@ -5843,7 +5905,7 @@ export class BridgeSessionCore {
5843
5905
  record.createdAt = Date.now();
5844
5906
  clampCodexAuthListOffset(record);
5845
5907
  if (record.messageId !== null) {
5846
- await this.editMessage(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));
5908
+ 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
5909
  }
5848
5910
  }
5849
5911
  async handleAuthToggleCallback(event, localId, index, locale) {
@@ -5868,7 +5930,7 @@ export class BridgeSessionCore {
5868
5930
  clampCodexAuthListOffset(record);
5869
5931
  await this.messaging.answerCallback(event.callbackQueryId, t(locale, disabled ? 'auth_candidate_disabled_short' : 'auth_candidate_enabled_short'));
5870
5932
  if (record.messageId !== null) {
5871
- await this.editMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
5933
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record), authChoiceKeyboard(locale, record));
5872
5934
  }
5873
5935
  }
5874
5936
  async handleAuthSwitchCallback(event, localId, index, locale) {
@@ -5898,7 +5960,7 @@ export class BridgeSessionCore {
5898
5960
  const switchLabels = await this.readCodexAuthSwitchLabels(candidate);
5899
5961
  const switchingMessage = t(locale, 'auth_switching', this.codexAuthSwitchParams(locale, switchLabels.fromLabel, switchLabels.toLabel));
5900
5962
  if (record.messageId !== null) {
5901
- await this.editMessage(event.scopeId, record.messageId, switchingMessage, []);
5963
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, switchingMessage, []);
5902
5964
  }
5903
5965
  const outcome = await this.switchCodexAuthAndRestart(event.scopeId, locale, candidate, false, false);
5904
5966
  const state = await this.listCodexAuthState();
@@ -5906,7 +5968,7 @@ export class BridgeSessionCore {
5906
5968
  record.createdAt = Date.now();
5907
5969
  clampCodexAuthListOffset(record);
5908
5970
  if (record.messageId !== null) {
5909
- await this.editMessage(event.scopeId, record.messageId, [
5971
+ await this.editAuthPanelMessage(event.scopeId, record.messageId, [
5910
5972
  ...this.formatAuthSwitchValidationLines(locale, outcome),
5911
5973
  renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record),
5912
5974
  ].filter(Boolean).join('\n\n'), authChoiceKeyboard(locale, record));
@@ -6839,6 +6901,7 @@ export class BridgeSessionCore {
6839
6901
  ? buildThreadsKeyboard(locale, threadLikes, binding.threadId)
6840
6902
  : buildThreadListKeyboard(locale, threadLikes, listState, binding.threadId);
6841
6903
  await this.editHtmlMessage(scopeId, event.messageId, text, keyboard);
6904
+ this.scheduleStalePanelDeletion(scopeId, event.messageId);
6842
6905
  }
6843
6906
  let callbackText = t(locale, 'thread_opened');
6844
6907
  if (this.config.codexAppSyncOnOpen) {
@@ -7171,9 +7234,11 @@ export class BridgeSessionCore {
7171
7234
  }
7172
7235
  if (messageId !== undefined) {
7173
7236
  await this.editMessage(scopeId, messageId, text, whereKeyboard(locale, false));
7237
+ this.scheduleStalePanelDeletion(scopeId, messageId);
7174
7238
  return;
7175
7239
  }
7176
- await this.sendMessage(scopeId, text, whereKeyboard(locale, false));
7240
+ const sentMessageId = await this.sendMessage(scopeId, text, whereKeyboard(locale, false));
7241
+ this.scheduleStalePanelDeletion(scopeId, sentMessageId);
7177
7242
  return;
7178
7243
  }
7179
7244
  const readyBinding = await this.ensureThreadReady(scopeId, binding);
@@ -7185,9 +7250,11 @@ export class BridgeSessionCore {
7185
7250
  }
7186
7251
  if (messageId !== undefined) {
7187
7252
  await this.editMessage(scopeId, messageId, text, whereKeyboard(locale, false));
7253
+ this.scheduleStalePanelDeletion(scopeId, messageId);
7188
7254
  return;
7189
7255
  }
7190
- await this.sendMessage(scopeId, text, whereKeyboard(locale, false));
7256
+ const sentMessageId = await this.sendMessage(scopeId, text, whereKeyboard(locale, false));
7257
+ this.scheduleStalePanelDeletion(scopeId, sentMessageId);
7191
7258
  return;
7192
7259
  }
7193
7260
  let text = formatWhereMessage(locale, thread, settings, this.config.defaultCwd, access, fastStatus);
@@ -7196,9 +7263,11 @@ export class BridgeSessionCore {
7196
7263
  }
7197
7264
  if (messageId !== undefined) {
7198
7265
  await this.editMessage(scopeId, messageId, text, whereKeyboard(locale, true));
7266
+ this.scheduleStalePanelDeletion(scopeId, messageId);
7199
7267
  return;
7200
7268
  }
7201
- await this.sendMessage(scopeId, text, whereKeyboard(locale, true));
7269
+ const sentMessageId = await this.sendMessage(scopeId, text, whereKeyboard(locale, true));
7270
+ this.scheduleStalePanelDeletion(scopeId, sentMessageId);
7202
7271
  }
7203
7272
  async handleThreadListNavigationCallback(event, action, locale) {
7204
7273
  const state = this.threadListPresentationState.get(event.scopeId) ?? {
@@ -7289,9 +7358,11 @@ export class BridgeSessionCore {
7289
7358
  : buildThreadListKeyboard(locale, forDisplay, presentationState, binding?.threadId ?? null);
7290
7359
  if (messageId !== undefined) {
7291
7360
  await this.editHtmlMessage(scopeId, messageId, text, keyboard);
7361
+ this.scheduleStalePanelDeletion(scopeId, messageId);
7292
7362
  return;
7293
7363
  }
7294
- await this.sendHtmlMessage(scopeId, text, keyboard);
7364
+ const sentMessageId = await this.sendHtmlMessage(scopeId, text, keyboard);
7365
+ this.scheduleStalePanelDeletion(scopeId, sentMessageId);
7295
7366
  }
7296
7367
  async showModelSettingsPanel(scopeId, messageId, locale = this.localeForChat(scopeId)) {
7297
7368
  const models = await this.app.listModels();
@@ -7304,9 +7375,11 @@ export class BridgeSessionCore {
7304
7375
  const keyboard = buildModelSettingsKeyboard(locale, models, settings);
7305
7376
  if (messageId !== undefined) {
7306
7377
  await this.editHtmlMessage(scopeId, messageId, text, keyboard);
7378
+ this.scheduleStalePanelDeletion(scopeId, messageId);
7307
7379
  return;
7308
7380
  }
7309
- await this.sendHtmlMessage(scopeId, text, keyboard);
7381
+ const sentMessageId = await this.sendHtmlMessage(scopeId, text, keyboard);
7382
+ this.scheduleStalePanelDeletion(scopeId, sentMessageId);
7310
7383
  }
7311
7384
  async showSetupPanel(scopeId, focus, messageId, locale = this.localeForChat(scopeId)) {
7312
7385
  const models = await this.app.listModels();
@@ -7321,9 +7394,11 @@ export class BridgeSessionCore {
7321
7394
  const keyboard = buildSetupPanelKeyboard(locale, { focus, models, settings, access });
7322
7395
  if (messageId !== undefined) {
7323
7396
  await this.editHtmlMessage(scopeId, messageId, text, keyboard);
7397
+ this.scheduleStalePanelDeletion(scopeId, messageId);
7324
7398
  return;
7325
7399
  }
7326
- await this.sendHtmlMessage(scopeId, text, keyboard);
7400
+ const sentMessageId = await this.sendHtmlMessage(scopeId, text, keyboard);
7401
+ this.scheduleStalePanelDeletion(scopeId, sentMessageId);
7327
7402
  }
7328
7403
  async showAccessSettingsPanel(scopeId, messageId, locale = this.localeForChat(scopeId)) {
7329
7404
  const access = this.resolveEffectiveAccess(scopeId);
@@ -7335,9 +7410,11 @@ export class BridgeSessionCore {
7335
7410
  const keyboard = buildAccessSettingsKeyboard(locale, access);
7336
7411
  if (messageId !== undefined) {
7337
7412
  await this.editHtmlMessage(scopeId, messageId, text, keyboard);
7413
+ this.scheduleStalePanelDeletion(scopeId, messageId);
7338
7414
  return;
7339
7415
  }
7340
- await this.sendHtmlMessage(scopeId, text, keyboard);
7416
+ const sentMessageId = await this.sendHtmlMessage(scopeId, text, keyboard);
7417
+ this.scheduleStalePanelDeletion(scopeId, sentMessageId);
7341
7418
  }
7342
7419
  async handleSettingsCallback(event, kind, rawValue, locale) {
7343
7420
  await this.handleSetupCallback(event, kind, rawValue, locale);
@@ -9004,12 +9081,14 @@ function parseRichAuthCandidateRow(line) {
9004
9081
  if (!name) {
9005
9082
  return null;
9006
9083
  }
9007
- const quotas = parts.map(formatRichAuthQuotaCell);
9084
+ const quotas = parts.map(parseRichAuthQuotaCell);
9008
9085
  const statusParts = splitRichAuthStatus(status);
9009
9086
  return {
9010
9087
  index: match[1],
9011
- quotaA: quotas[0] ?? '-',
9012
- quotaB: quotas[1] ?? '-',
9088
+ quotaA: quotas[0]?.value ?? '-',
9089
+ quotaAReset: quotas[0]?.reset ?? '-',
9090
+ quotaB: quotas[1]?.value ?? '-',
9091
+ quotaBReset: quotas[1]?.reset ?? '-',
9013
9092
  name,
9014
9093
  current,
9015
9094
  enabled: statusParts.health === 'disabled' || statusParts.health === '已禁用'
@@ -9021,12 +9100,14 @@ function parseRichAuthCandidateRow(line) {
9021
9100
  function formatRichAuthCandidateTable(rows) {
9022
9101
  return [
9023
9102
  '<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>',
9103
+ '<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
9104
  ...rows.map(row => [
9026
9105
  '<tr>',
9027
9106
  `<td>${escapeTelegramHtml(row.index)}</td>`,
9028
9107
  `<td>${escapeTelegramHtml(row.quotaA)}</td>`,
9108
+ `<td>${escapeTelegramHtml(row.quotaAReset)}</td>`,
9029
9109
  `<td>${escapeTelegramHtml(row.quotaB)}</td>`,
9110
+ `<td>${escapeTelegramHtml(row.quotaBReset)}</td>`,
9030
9111
  `<td>${formatRichAuthCommandLink(`/auth use ${row.index}`, row.name)}</td>`,
9031
9112
  `<td>${row.current ? 'yes' : '-'}</td>`,
9032
9113
  `<td>${escapeTelegramHtml(row.plan)}</td>`,
@@ -9077,16 +9158,17 @@ function formatRichAuthRisk(health) {
9077
9158
  }
9078
9159
  return '-';
9079
9160
  }
9080
- function formatRichAuthQuotaCell(value) {
9161
+ function parseRichAuthQuotaCell(value) {
9081
9162
  const trimmed = value.trim();
9082
9163
  if (!trimmed || trimmed === '--' || trimmed === '—') {
9083
- return '-';
9164
+ return { value: '-', reset: '-' };
9084
9165
  }
9085
- const [windowLabel, percent] = trimmed.split(':');
9166
+ const [quotaPart, reset = '-'] = trimmed.split('@', 2);
9167
+ const [windowLabel, percent] = quotaPart.split(':');
9086
9168
  if (!windowLabel || percent === undefined) {
9087
- return trimmed;
9169
+ return { value: quotaPart, reset };
9088
9170
  }
9089
- return `${percent}%`;
9171
+ return { value: `${percent}%`, reset };
9090
9172
  }
9091
9173
  function formatRichAuthCommandCell(row) {
9092
9174
  const commands = [formatRichAuthCommandLink(`/auth use ${row.index}`, 'use')];
@@ -9314,6 +9396,12 @@ function formatConfigMessage(locale, result, appConfig, authPoolStats) {
9314
9396
  value: t(locale, appConfig.authAutoDeleteNeedsRepair ? 'yes' : 'no'),
9315
9397
  }));
9316
9398
  lines.push(`AUTH_AUTO_DELETE_NEEDS_REPAIR=${appConfig.authAutoDeleteNeedsRepair ? 'true' : 'false'}`);
9399
+ lines.push(t(locale, 'config_delete_tool_details_after_final', {
9400
+ value: t(locale, appConfig.telegramDeleteToolDetailsAfterFinal ? 'yes' : 'no'),
9401
+ }));
9402
+ lines.push(`TELEGRAM_DELETE_TOOL_DETAILS_AFTER_FINAL=${appConfig.telegramDeleteToolDetailsAfterFinal ? 'true' : 'false'}`);
9403
+ lines.push(t(locale, 'config_panel_ttl', { value: formatDurationMs(locale, appConfig.telegramPanelTtlMs) }));
9404
+ lines.push(`TELEGRAM_PANEL_TTL_MS=${appConfig.telegramPanelTtlMs}`);
9317
9405
  lines.push(formatCodexAuthPoolSummary(locale, authPoolStats));
9318
9406
  return lines.join('\n');
9319
9407
  }
@@ -9328,11 +9416,32 @@ function isInvalidCodexAuthDeleteReason(reason) {
9328
9416
  return reason === AUTH_DELETE_REASON_NEEDS_REPAIR;
9329
9417
  }
9330
9418
  function configKeyboard(locale, appConfig) {
9331
- const enabled = appConfig.authAutoDeleteNeedsRepair;
9332
- return [[{
9333
- text: t(locale, enabled ? 'button_config_auth_auto_delete_off' : 'button_config_auth_auto_delete_on'),
9334
- callback_data: `config:auth_auto_delete:${enabled ? 'off' : 'on'}`,
9335
- }]];
9419
+ const authAutoDeleteEnabled = appConfig.authAutoDeleteNeedsRepair;
9420
+ const deleteToolDetailsEnabled = appConfig.telegramDeleteToolDetailsAfterFinal;
9421
+ return [
9422
+ [{
9423
+ text: t(locale, authAutoDeleteEnabled ? 'button_config_auth_auto_delete_off' : 'button_config_auth_auto_delete_on'),
9424
+ callback_data: `config:auth_auto_delete:${authAutoDeleteEnabled ? 'off' : 'on'}`,
9425
+ }],
9426
+ [{
9427
+ text: t(locale, deleteToolDetailsEnabled ? 'button_config_delete_tool_details_off' : 'button_config_delete_tool_details_on'),
9428
+ callback_data: `config:delete_tool_details:${deleteToolDetailsEnabled ? 'off' : 'on'}`,
9429
+ }],
9430
+ ];
9431
+ }
9432
+ function formatDurationMs(locale, value) {
9433
+ if (value <= 0) {
9434
+ return locale === 'zh' ? '关闭' : 'disabled';
9435
+ }
9436
+ if (value % 60_000 === 0) {
9437
+ const minutes = value / 60_000;
9438
+ return locale === 'zh' ? `${minutes} 分钟` : `${minutes} min`;
9439
+ }
9440
+ if (value % 1000 === 0) {
9441
+ const seconds = value / 1000;
9442
+ return locale === 'zh' ? `${seconds} 秒` : `${seconds} sec`;
9443
+ }
9444
+ return `${value}ms`;
9336
9445
  }
9337
9446
  function parseConfigBooleanArg(value) {
9338
9447
  const normalized = value?.trim().toLowerCase();
@@ -11135,8 +11244,10 @@ function authQuotaSnapshotFromRateLimit(snapshot, accountId = null, quotaIdentit
11135
11244
  planType: snapshot.planType,
11136
11245
  primaryWindowDurationMins: snapshot.primary?.windowDurationMins ?? null,
11137
11246
  primaryRemainingPercent: snapshot.primary ? remainingUsagePercent(snapshot.primary.usedPercent) : null,
11247
+ primaryResetsAt: snapshot.primary?.resetsAt ?? null,
11138
11248
  secondaryWindowDurationMins: snapshot.secondary?.windowDurationMins ?? null,
11139
11249
  secondaryRemainingPercent: snapshot.secondary ? remainingUsagePercent(snapshot.secondary.usedPercent) : null,
11250
+ secondaryResetsAt: snapshot.secondary?.resetsAt ?? null,
11140
11251
  };
11141
11252
  }
11142
11253
  function codexAuthQuotaSnapshotFromRecord(record) {
@@ -11147,8 +11258,10 @@ function codexAuthQuotaSnapshotFromRecord(record) {
11147
11258
  planType: record.planType,
11148
11259
  primaryWindowDurationMins: record.primaryWindowDurationMins,
11149
11260
  primaryRemainingPercent: record.primaryRemainingPercent,
11261
+ primaryResetsAt: record.primaryResetsAt,
11150
11262
  secondaryWindowDurationMins: record.secondaryWindowDurationMins,
11151
11263
  secondaryRemainingPercent: record.secondaryRemainingPercent,
11264
+ secondaryResetsAt: record.secondaryResetsAt,
11152
11265
  };
11153
11266
  }
11154
11267
  function mergeCodexAuthQuotaSnapshots(current, incoming) {
@@ -11171,8 +11284,10 @@ function isFiniteCodexAuthQuotaSnapshotRecord(record) {
11171
11284
  && isNullableString(record.planType)
11172
11285
  && isNullableFiniteNumber(record.primaryWindowDurationMins)
11173
11286
  && isNullableFiniteNumber(record.primaryRemainingPercent)
11287
+ && isNullableFiniteNumber(record.primaryResetsAt)
11174
11288
  && isNullableFiniteNumber(record.secondaryWindowDurationMins)
11175
- && isNullableFiniteNumber(record.secondaryRemainingPercent);
11289
+ && isNullableFiniteNumber(record.secondaryRemainingPercent)
11290
+ && isNullableFiniteNumber(record.secondaryResetsAt);
11176
11291
  }
11177
11292
  function remainingUsagePercent(usedPercent) {
11178
11293
  if (!Number.isFinite(usedPercent)) {
@@ -11185,12 +11300,12 @@ function formatAuthQuotaPrefix(locale, snapshot) {
11185
11300
  return '--';
11186
11301
  }
11187
11302
  const windows = [
11188
- [snapshot.primaryWindowDurationMins, snapshot.primaryRemainingPercent, 'primary'],
11189
- [snapshot.secondaryWindowDurationMins, snapshot.secondaryRemainingPercent, 'secondary'],
11303
+ [snapshot.primaryWindowDurationMins, snapshot.primaryRemainingPercent, snapshot.primaryResetsAt, 'primary'],
11304
+ [snapshot.secondaryWindowDurationMins, snapshot.secondaryRemainingPercent, snapshot.secondaryResetsAt, 'secondary'],
11190
11305
  ];
11191
11306
  const values = windows
11192
- .filter(([duration, remaining]) => duration !== null || remaining !== null)
11193
- .map(([duration, remaining, fallback]) => (`${formatCompactRateLimitWindowLabel(locale, duration, fallback)}:${remaining === null ? '--' : formatUsagePercent(remaining)}`));
11307
+ .filter(([duration, remaining, resetsAt]) => duration !== null || remaining !== null || resetsAt !== null)
11308
+ .map(([duration, remaining, resetsAt, fallback]) => (`${formatCompactRateLimitWindowLabel(locale, duration, fallback)}:${remaining === null ? '--' : formatUsagePercent(remaining)}${resetsAt === null ? '' : `@${formatLocalTimestamp(resetsAt)}`}`));
11194
11309
  return values.length > 0 ? values.join('|') : '--';
11195
11310
  }
11196
11311
  function formatAuthQuotaButtonPrefix(snapshot) {
@@ -11216,8 +11331,10 @@ function isCodexAuthQuotaSnapshot(value) {
11216
11331
  && (snapshot.planType === undefined || isNullableString(snapshot.planType))
11217
11332
  && (snapshot.primaryWindowDurationMins === undefined || isNullableFiniteNumber(snapshot.primaryWindowDurationMins))
11218
11333
  && isNullableFiniteNumber(snapshot.primaryRemainingPercent)
11334
+ && (snapshot.primaryResetsAt === undefined || isNullableFiniteNumber(snapshot.primaryResetsAt))
11219
11335
  && (snapshot.secondaryWindowDurationMins === undefined || isNullableFiniteNumber(snapshot.secondaryWindowDurationMins))
11220
- && isNullableFiniteNumber(snapshot.secondaryRemainingPercent);
11336
+ && isNullableFiniteNumber(snapshot.secondaryRemainingPercent)
11337
+ && (snapshot.secondaryResetsAt === undefined || isNullableFiniteNumber(snapshot.secondaryResetsAt));
11221
11338
  }
11222
11339
  function normalizeCodexAuthQuotaSnapshot(snapshot) {
11223
11340
  return {
@@ -11227,8 +11344,10 @@ function normalizeCodexAuthQuotaSnapshot(snapshot) {
11227
11344
  planType: snapshot.planType ?? null,
11228
11345
  primaryWindowDurationMins: snapshot.primaryWindowDurationMins ?? null,
11229
11346
  primaryRemainingPercent: snapshot.primaryRemainingPercent,
11347
+ primaryResetsAt: snapshot.primaryResetsAt ?? null,
11230
11348
  secondaryWindowDurationMins: snapshot.secondaryWindowDurationMins ?? null,
11231
11349
  secondaryRemainingPercent: snapshot.secondaryRemainingPercent,
11350
+ secondaryResetsAt: snapshot.secondaryResetsAt ?? null,
11232
11351
  };
11233
11352
  }
11234
11353
  function chatGptAuthMetadataCompatible(left, right) {
package/dist/i18n.d.ts CHANGED
@@ -282,6 +282,8 @@ declare const MESSAGES: {
282
282
  readonly button_auth_filter_attention: "Attention";
283
283
  readonly button_config_auth_auto_delete_on: "Auto-delete on";
284
284
  readonly button_config_auth_auto_delete_off: "Auto-delete off";
285
+ readonly button_config_delete_tool_details_on: "Delete tool details";
286
+ readonly button_config_delete_tool_details_off: "Keep tool details";
285
287
  readonly another_turn_running: "Another turn is already running. Use /interrupt, /takeover, /queue, or wait.";
286
288
  readonly working: "Working...";
287
289
  readonly usage_open: "Usage: /open <n>";
@@ -617,6 +619,10 @@ declare const MESSAGES: {
617
619
  readonly config_auth_auto_delete_needs_repair: "Auto-delete unrecoverable auth candidates: {value}";
618
620
  readonly config_auth_auto_delete_updated: "Auto-delete unrecoverable auth candidates set to: {value}";
619
621
  readonly config_auth_auto_delete_usage: "Usage: /config auth_auto_delete <on|off>";
622
+ readonly config_delete_tool_details_after_final: "Delete operation details after final reply: {value}";
623
+ readonly config_delete_tool_details_updated: "Delete operation details after final reply set to: {value}";
624
+ readonly config_delete_tool_details_usage: "Usage: /config delete_tool_details <on|off>";
625
+ readonly config_panel_ttl: "Interactive panel cleanup: {value}";
620
626
  readonly config_env_update_failed: "Runtime setting changed, but updating {value} failed: {error}";
621
627
  readonly requirements_title: "Config requirements";
622
628
  readonly requirements_empty: "No config requirements are configured.";
@@ -982,6 +988,8 @@ declare const MESSAGES: {
982
988
  readonly button_auth_filter_attention: "需关注";
983
989
  readonly button_config_auth_auto_delete_on: "开启自动剔除";
984
990
  readonly button_config_auth_auto_delete_off: "关闭自动剔除";
991
+ readonly button_config_delete_tool_details_on: "删除操作明细";
992
+ readonly button_config_delete_tool_details_off: "保留操作明细";
985
993
  readonly another_turn_running: "已经有一个回复在进行中。请先等待,或使用 /interrupt、/takeover、/queue。";
986
994
  readonly working: "处理中...";
987
995
  readonly usage_open: "用法:/open <编号>";
@@ -1317,6 +1325,10 @@ declare const MESSAGES: {
1317
1325
  readonly config_auth_auto_delete_needs_repair: "自动剔除无法恢复的 auth 候选:{value}";
1318
1326
  readonly config_auth_auto_delete_updated: "自动剔除无法恢复的 auth 候选已设置为:{value}";
1319
1327
  readonly config_auth_auto_delete_usage: "用法:/config auth_auto_delete <on|off>";
1328
+ readonly config_delete_tool_details_after_final: "最终回复后删除操作明细:{value}";
1329
+ readonly config_delete_tool_details_updated: "最终回复后删除操作明细已设置为:{value}";
1330
+ readonly config_delete_tool_details_usage: "用法:/config delete_tool_details <on|off>";
1331
+ readonly config_panel_ttl: "交互面板自动清理:{value}";
1320
1332
  readonly config_env_update_failed: "运行时设置已改变,但更新 {value} 失败:{error}";
1321
1333
  readonly requirements_title: "配置要求";
1322
1334
  readonly requirements_empty: "当前没有配置要求。";
package/dist/i18n.js CHANGED
@@ -280,6 +280,8 @@ const MESSAGES = {
280
280
  button_auth_filter_attention: 'Attention',
281
281
  button_config_auth_auto_delete_on: 'Auto-delete on',
282
282
  button_config_auth_auto_delete_off: 'Auto-delete off',
283
+ button_config_delete_tool_details_on: 'Delete tool details',
284
+ button_config_delete_tool_details_off: 'Keep tool details',
283
285
  another_turn_running: 'Another turn is already running. Use /interrupt, /takeover, /queue, or wait.',
284
286
  working: 'Working...',
285
287
  usage_open: 'Usage: /open <n>',
@@ -615,6 +617,10 @@ const MESSAGES = {
615
617
  config_auth_auto_delete_needs_repair: 'Auto-delete unrecoverable auth candidates: {value}',
616
618
  config_auth_auto_delete_updated: 'Auto-delete unrecoverable auth candidates set to: {value}',
617
619
  config_auth_auto_delete_usage: 'Usage: /config auth_auto_delete <on|off>',
620
+ config_delete_tool_details_after_final: 'Delete operation details after final reply: {value}',
621
+ config_delete_tool_details_updated: 'Delete operation details after final reply set to: {value}',
622
+ config_delete_tool_details_usage: 'Usage: /config delete_tool_details <on|off>',
623
+ config_panel_ttl: 'Interactive panel cleanup: {value}',
618
624
  config_env_update_failed: 'Runtime setting changed, but updating {value} failed: {error}',
619
625
  requirements_title: 'Config requirements',
620
626
  requirements_empty: 'No config requirements are configured.',
@@ -980,6 +986,8 @@ const MESSAGES = {
980
986
  button_auth_filter_attention: '需关注',
981
987
  button_config_auth_auto_delete_on: '开启自动剔除',
982
988
  button_config_auth_auto_delete_off: '关闭自动剔除',
989
+ button_config_delete_tool_details_on: '删除操作明细',
990
+ button_config_delete_tool_details_off: '保留操作明细',
983
991
  another_turn_running: '已经有一个回复在进行中。请先等待,或使用 /interrupt、/takeover、/queue。',
984
992
  working: '处理中...',
985
993
  usage_open: '用法:/open <编号>',
@@ -1315,6 +1323,10 @@ const MESSAGES = {
1315
1323
  config_auth_auto_delete_needs_repair: '自动剔除无法恢复的 auth 候选:{value}',
1316
1324
  config_auth_auto_delete_updated: '自动剔除无法恢复的 auth 候选已设置为:{value}',
1317
1325
  config_auth_auto_delete_usage: '用法:/config auth_auto_delete <on|off>',
1326
+ config_delete_tool_details_after_final: '最终回复后删除操作明细:{value}',
1327
+ config_delete_tool_details_updated: '最终回复后删除操作明细已设置为:{value}',
1328
+ config_delete_tool_details_usage: '用法:/config delete_tool_details <on|off>',
1329
+ config_panel_ttl: '交互面板自动清理:{value}',
1318
1330
  config_env_update_failed: '运行时设置已改变,但更新 {value} 失败:{error}',
1319
1331
  requirements_title: '配置要求',
1320
1332
  requirements_empty: '当前没有配置要求。',
@@ -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
  }
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxden-app/foxclaw",
3
- "version": "0.5.58",
3
+ "version": "0.5.61",
4
4
  "description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",